Improved math expressions (#6)
* Improved math expressions Things are in a better state, + - / * work now for basic const var = 5 <operator> 1 Though the current method I'm using to make the ast isn't really going to work for dealing with precedence rules so some refactoring is needed going forward * get complex math expressions working with precedence including parans Node that identifiers are working, call expressions are not, that's a TODO / * % + - are working both other things like exponent and logical operators are also not working. Recasting is the most important thing to implement next * get recasting working for nested binary expressions * clean up
This commit is contained in:
@ -2,6 +2,7 @@ import {
|
|||||||
abstractSyntaxTree,
|
abstractSyntaxTree,
|
||||||
findClosingBrace,
|
findClosingBrace,
|
||||||
hasPipeOperator,
|
hasPipeOperator,
|
||||||
|
findEndOfBinaryExpression,
|
||||||
} from './abstractSyntaxTree'
|
} from './abstractSyntaxTree'
|
||||||
import { lexer } from './tokeniser'
|
import { lexer } from './tokeniser'
|
||||||
|
|
||||||
@ -1521,3 +1522,334 @@ describe('testing pipe operator special', () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { PathToNode } from './executor'
|
import { PathToNode } from './executor'
|
||||||
import { Token } from './tokeniser'
|
import { Token } from './tokeniser'
|
||||||
|
import { parseExpression } from './astMathExpressions'
|
||||||
|
|
||||||
type syntaxType =
|
type syntaxType =
|
||||||
| 'Program'
|
| 'Program'
|
||||||
@ -473,8 +474,7 @@ function makeVariableDeclarators(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BinaryPart = Literal | Identifier
|
export type BinaryPart = Literal | Identifier | BinaryExpression
|
||||||
// | BinaryExpression
|
|
||||||
// | CallExpression
|
// | CallExpression
|
||||||
// | MemberExpression
|
// | MemberExpression
|
||||||
// | ArrayExpression
|
// | ArrayExpression
|
||||||
@ -764,7 +764,7 @@ export interface BinaryExpression extends GeneralStatement {
|
|||||||
function makeBinaryPart(
|
function makeBinaryPart(
|
||||||
tokens: Token[],
|
tokens: Token[],
|
||||||
index: number
|
index: number
|
||||||
): { part: BinaryPart; lastIndex: number } {
|
): { part: Literal | Identifier; lastIndex: number } {
|
||||||
const currentToken = tokens[index]
|
const currentToken = tokens[index]
|
||||||
if (currentToken.type === 'word') {
|
if (currentToken.type === 'word') {
|
||||||
const identifier = makeIdentifier(tokens, index)
|
const identifier = makeIdentifier(tokens, index)
|
||||||
@ -783,28 +783,43 @@ function makeBinaryPart(
|
|||||||
throw new Error('Expected a previous BinaryPart if statement to match')
|
throw new Error('Expected a previous BinaryPart if statement to match')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function findEndOfBinaryExpression(
|
||||||
|
tokens: Token[],
|
||||||
|
index: number
|
||||||
|
): number {
|
||||||
|
const currentToken = tokens[index]
|
||||||
|
if (currentToken.type === 'brace' && currentToken.value === '(') {
|
||||||
|
const closingParenthesis = findClosingBrace(tokens, index)
|
||||||
|
const maybeAnotherOperator = nextMeaningfulToken(tokens, closingParenthesis)
|
||||||
|
if (
|
||||||
|
maybeAnotherOperator?.token?.type !== 'operator' ||
|
||||||
|
maybeAnotherOperator?.token?.value === '|>'
|
||||||
|
) {
|
||||||
|
return closingParenthesis
|
||||||
|
}
|
||||||
|
const nextRight = nextMeaningfulToken(tokens, maybeAnotherOperator.index)
|
||||||
|
return findEndOfBinaryExpression(tokens, nextRight.index)
|
||||||
|
}
|
||||||
|
const maybeOperator = nextMeaningfulToken(tokens, index)
|
||||||
|
if (
|
||||||
|
maybeOperator?.token?.type !== 'operator' ||
|
||||||
|
maybeOperator?.token?.value === '|>'
|
||||||
|
) {
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
const nextRight = nextMeaningfulToken(tokens, maybeOperator.index)
|
||||||
|
return findEndOfBinaryExpression(tokens, nextRight.index)
|
||||||
|
}
|
||||||
|
|
||||||
function makeBinaryExpression(
|
function makeBinaryExpression(
|
||||||
tokens: Token[],
|
tokens: Token[],
|
||||||
index: number
|
index: number
|
||||||
): { expression: BinaryExpression; lastIndex: number } {
|
): { expression: BinaryExpression; lastIndex: number } {
|
||||||
const currentToken = tokens[index]
|
const endIndex = findEndOfBinaryExpression(tokens, index)
|
||||||
const { part: left } = makeBinaryPart(tokens, index)
|
const expression = parseExpression(tokens.slice(index, endIndex + 1))
|
||||||
const { token: operatorToken, index: operatorIndex } = nextMeaningfulToken(
|
|
||||||
tokens,
|
|
||||||
index
|
|
||||||
)
|
|
||||||
const rightToken = nextMeaningfulToken(tokens, operatorIndex)
|
|
||||||
const { part: right } = makeBinaryPart(tokens, rightToken.index)
|
|
||||||
return {
|
return {
|
||||||
expression: {
|
expression,
|
||||||
type: 'BinaryExpression',
|
lastIndex: endIndex,
|
||||||
start: currentToken.start,
|
|
||||||
end: right.end,
|
|
||||||
left,
|
|
||||||
operator: operatorToken.value,
|
|
||||||
right,
|
|
||||||
},
|
|
||||||
lastIndex: rightToken.index,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1296,6 +1311,61 @@ export function findClosingBrace(
|
|||||||
return findClosingBrace(tokens, index + 1, _braceCount, searchOpeningBrace)
|
return findClosingBrace(tokens, index + 1, _braceCount, searchOpeningBrace)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// function findOpeningBrace(
|
||||||
|
// tokens: Token[],
|
||||||
|
// index: number,
|
||||||
|
// _braceCount: number = 0,
|
||||||
|
// _searchClosingBrace: string = ''
|
||||||
|
// ): number {
|
||||||
|
// // should be called with the index of the opening brace
|
||||||
|
// const closingBraceMap: { [key: string]: string } = {
|
||||||
|
// ')': '(',
|
||||||
|
// '}': '{',
|
||||||
|
// ']': '[',
|
||||||
|
// }
|
||||||
|
// const currentToken = tokens[index]
|
||||||
|
// let searchClosingBrace = _searchClosingBrace
|
||||||
|
|
||||||
|
// const isFirstCall = !searchClosingBrace && _braceCount === 0
|
||||||
|
// if (isFirstCall) {
|
||||||
|
// searchClosingBrace = currentToken.value
|
||||||
|
// if (![')', '}', ']'].includes(searchClosingBrace)) {
|
||||||
|
// throw new Error(
|
||||||
|
// `expected to be started on a opening brace ( { [, instead found '${searchClosingBrace}'`
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const foundOpeningBrace =
|
||||||
|
// _braceCount === 1 &&
|
||||||
|
// currentToken.value === closingBraceMap[searchClosingBrace]
|
||||||
|
// const foundAnotherClosingBrace = currentToken.value === searchClosingBrace
|
||||||
|
// const foundAnotherOpeningBrace =
|
||||||
|
// currentToken.value === closingBraceMap[searchClosingBrace]
|
||||||
|
|
||||||
|
// if (foundOpeningBrace) {
|
||||||
|
// return index
|
||||||
|
// }
|
||||||
|
// if (foundAnotherClosingBrace) {
|
||||||
|
// return findOpeningBrace(
|
||||||
|
// tokens,
|
||||||
|
// index - 1,
|
||||||
|
// _braceCount + 1,
|
||||||
|
// searchClosingBrace
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// if (foundAnotherOpeningBrace) {
|
||||||
|
// return findOpeningBrace(
|
||||||
|
// tokens,
|
||||||
|
// index - 1,
|
||||||
|
// _braceCount - 1,
|
||||||
|
// searchClosingBrace
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// // non-brace token, increment and continue
|
||||||
|
// return findOpeningBrace(tokens, index - 1, _braceCount, searchClosingBrace)
|
||||||
|
// }
|
||||||
|
|
||||||
function isCallExpression(tokens: Token[], index: number): number {
|
function isCallExpression(tokens: Token[], index: number): number {
|
||||||
const currentToken = tokens[index]
|
const currentToken = tokens[index]
|
||||||
const veryNextToken = tokens[index + 1] // i.e. no whitespace
|
const veryNextToken = tokens[index + 1] // i.e. no whitespace
|
||||||
|
204
src/lang/astMathExpressions.test.ts
Normal file
204
src/lang/astMathExpressions.test.ts
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
import { parseExpression, reversePolishNotation } from './astMathExpressions'
|
||||||
|
import { lexer } from './tokeniser'
|
||||||
|
|
||||||
|
describe('parseExpression', () => {
|
||||||
|
it('parses a simple expression', () => {
|
||||||
|
const result = parseExpression(lexer('1 + 2'))
|
||||||
|
expect(result).toEqual({
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
operator: '+',
|
||||||
|
start: 0,
|
||||||
|
end: 5,
|
||||||
|
left: { type: 'Literal', value: 1, raw: '1', start: 0, end: 1 },
|
||||||
|
right: { type: 'Literal', value: 2, raw: '2', start: 4, end: 5 },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('parses a more complex expression + followed by *', () => {
|
||||||
|
const tokens = lexer('1 + 2 * 3')
|
||||||
|
const result = parseExpression(tokens)
|
||||||
|
expect(result).toEqual({
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
operator: '+',
|
||||||
|
start: 0,
|
||||||
|
end: 9,
|
||||||
|
left: { type: 'Literal', value: 1, raw: '1', start: 0, end: 1 },
|
||||||
|
right: {
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
operator: '*',
|
||||||
|
start: 4,
|
||||||
|
end: 9,
|
||||||
|
left: { type: 'Literal', value: 2, raw: '2', start: 4, end: 5 },
|
||||||
|
right: { type: 'Literal', value: 3, raw: '3', start: 8, end: 9 },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('parses a more complex expression with parentheses: 1 * ( 2 + 3 )', () => {
|
||||||
|
const result = parseExpression(lexer('1 * ( 2 + 3 )'))
|
||||||
|
expect(result).toEqual({
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
operator: '*',
|
||||||
|
start: 0,
|
||||||
|
end: 13,
|
||||||
|
left: { type: 'Literal', value: 1, raw: '1', start: 0, end: 1 },
|
||||||
|
right: {
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
operator: '+',
|
||||||
|
start: 6,
|
||||||
|
end: 11,
|
||||||
|
left: { type: 'Literal', value: 2, raw: '2', start: 6, end: 7 },
|
||||||
|
right: { type: 'Literal', value: 3, raw: '3', start: 10, end: 11 },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('parses a more complex expression with parentheses with more ', () => {
|
||||||
|
const result = parseExpression(lexer('1 * ( 2 + 3 ) / 4'))
|
||||||
|
expect(result).toEqual({
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
operator: '/',
|
||||||
|
start: 0,
|
||||||
|
end: 17,
|
||||||
|
left: {
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
operator: '*',
|
||||||
|
start: 0,
|
||||||
|
end: 13,
|
||||||
|
left: { type: 'Literal', value: 1, raw: '1', start: 0, end: 1 },
|
||||||
|
right: {
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
operator: '+',
|
||||||
|
start: 6,
|
||||||
|
end: 11,
|
||||||
|
left: { type: 'Literal', value: 2, raw: '2', start: 6, end: 7 },
|
||||||
|
right: { type: 'Literal', value: 3, raw: '3', start: 10, end: 11 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
right: { type: 'Literal', value: 4, raw: '4', start: 16, end: 17 },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('same as last one but with a 1 + at the start ', () => {
|
||||||
|
const result = parseExpression(lexer('1 + ( 2 + 3 ) / 4'))
|
||||||
|
expect(result).toEqual({
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
operator: '+',
|
||||||
|
start: 0,
|
||||||
|
end: 17,
|
||||||
|
left: { type: 'Literal', value: 1, raw: '1', start: 0, end: 1 },
|
||||||
|
right: {
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
operator: '/',
|
||||||
|
start: 4,
|
||||||
|
end: 17,
|
||||||
|
left: {
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
operator: '+',
|
||||||
|
start: 6,
|
||||||
|
end: 11,
|
||||||
|
left: { type: 'Literal', value: 2, raw: '2', start: 6, end: 7 },
|
||||||
|
right: { type: 'Literal', value: 3, raw: '3', start: 10, end: 11 },
|
||||||
|
},
|
||||||
|
right: { type: 'Literal', value: 4, raw: '4', start: 16, end: 17 },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('nested braces ', () => {
|
||||||
|
const result = parseExpression(lexer('1 * (( 2 + 3 ) / 4 + 5 )'))
|
||||||
|
expect(result).toEqual({
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
operator: '*',
|
||||||
|
start: 0,
|
||||||
|
end: 24,
|
||||||
|
left: { type: 'Literal', value: 1, raw: '1', start: 0, end: 1 },
|
||||||
|
right: {
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
operator: '+',
|
||||||
|
start: 5,
|
||||||
|
end: 22,
|
||||||
|
left: {
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
operator: '/',
|
||||||
|
start: 5,
|
||||||
|
end: 18,
|
||||||
|
left: {
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
operator: '+',
|
||||||
|
start: 7,
|
||||||
|
end: 12,
|
||||||
|
left: { type: 'Literal', value: 2, raw: '2', start: 7, end: 8 },
|
||||||
|
right: {
|
||||||
|
type: 'Literal',
|
||||||
|
value: 3,
|
||||||
|
raw: '3',
|
||||||
|
start: 11,
|
||||||
|
end: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
right: { type: 'Literal', value: 4, raw: '4', start: 17, end: 18 },
|
||||||
|
},
|
||||||
|
right: { type: 'Literal', value: 5, raw: '5', start: 21, end: 22 },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('multiple braces around the same thing ', () => {
|
||||||
|
const result = parseExpression(lexer('1 * ((( 2 + 3 )))'))
|
||||||
|
expect(result).toEqual({
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
operator: '*',
|
||||||
|
start: 0,
|
||||||
|
end: 17,
|
||||||
|
left: { type: 'Literal', value: 1, raw: '1', start: 0, end: 1 },
|
||||||
|
right: {
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
operator: '+',
|
||||||
|
start: 8,
|
||||||
|
end: 13,
|
||||||
|
left: { type: 'Literal', value: 2, raw: '2', start: 8, end: 9 },
|
||||||
|
right: { type: 'Literal', value: 3, raw: '3', start: 12, end: 13 },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('multiple braces around a sing literal', () => {
|
||||||
|
const code = '2 + (((3)))'
|
||||||
|
const result = parseExpression(lexer(code))
|
||||||
|
expect(result).toEqual({
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
operator: '+',
|
||||||
|
start: 0,
|
||||||
|
end: code.indexOf(')))') + 3,
|
||||||
|
left: { type: 'Literal', value: 2, raw: '2', start: 0, end: 1 },
|
||||||
|
right: { type: 'Literal', value: 3, raw: '3', start: 7, end: 8 },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('reversePolishNotation', () => {
|
||||||
|
it('converts a simple expression', () => {
|
||||||
|
const result = reversePolishNotation(lexer('1 + 2'))
|
||||||
|
expect(result).toEqual([
|
||||||
|
{ type: 'number', value: '1', start: 0, end: 1 },
|
||||||
|
{ type: 'number', value: '2', start: 4, end: 5 },
|
||||||
|
{ type: 'operator', value: '+', start: 2, end: 3 },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
it('converts a more complex expression', () => {
|
||||||
|
const result = reversePolishNotation(lexer('1 + 2 * 3'))
|
||||||
|
expect(result).toEqual([
|
||||||
|
{ type: 'number', value: '1', start: 0, end: 1 },
|
||||||
|
{ type: 'number', value: '2', start: 4, end: 5 },
|
||||||
|
{ type: 'number', value: '3', start: 8, end: 9 },
|
||||||
|
{ type: 'operator', value: '*', start: 6, end: 7 },
|
||||||
|
{ type: 'operator', value: '+', start: 2, end: 3 },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
it('converts a more complex expression with parentheses', () => {
|
||||||
|
const result = reversePolishNotation(lexer('1 * ( 2 + 3 )'))
|
||||||
|
expect(result).toEqual([
|
||||||
|
{ type: 'number', value: '1', start: 0, end: 1 },
|
||||||
|
{ type: 'brace', value: '(', start: 4, end: 5 },
|
||||||
|
{ type: 'number', value: '2', start: 6, end: 7 },
|
||||||
|
{ type: 'number', value: '3', start: 10, end: 11 },
|
||||||
|
{ type: 'operator', value: '+', start: 8, end: 9 },
|
||||||
|
{ type: 'brace', value: ')', start: 12, end: 13 },
|
||||||
|
{ type: 'operator', value: '*', start: 2, end: 3 },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
218
src/lang/astMathExpressions.ts
Normal file
218
src/lang/astMathExpressions.ts
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
import { BinaryExpression, Literal, Identifier } from './abstractSyntaxTree'
|
||||||
|
import { Token } from './tokeniser'
|
||||||
|
|
||||||
|
interface Tree {
|
||||||
|
value: string
|
||||||
|
left?: Tree
|
||||||
|
right?: Tree
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reversePolishNotation(
|
||||||
|
tokens: Token[],
|
||||||
|
previousPostfix: Token[] = [],
|
||||||
|
operators: Token[] = []
|
||||||
|
): Token[] {
|
||||||
|
if (tokens.length === 0) {
|
||||||
|
return [...previousPostfix, ...operators.slice().reverse()] // reverse mutates, so clone is needed
|
||||||
|
}
|
||||||
|
const currentToken = tokens[0]
|
||||||
|
if (
|
||||||
|
currentToken.type === 'number' ||
|
||||||
|
currentToken.type === 'word' ||
|
||||||
|
currentToken.type === 'string'
|
||||||
|
) {
|
||||||
|
return reversePolishNotation(
|
||||||
|
tokens.slice(1),
|
||||||
|
[...previousPostfix, currentToken],
|
||||||
|
operators
|
||||||
|
)
|
||||||
|
} else if (['+', '-', '*', '/', '%'].includes(currentToken.value)) {
|
||||||
|
if (
|
||||||
|
operators.length > 0 &&
|
||||||
|
_precedence(operators[operators.length - 1]) >= _precedence(currentToken)
|
||||||
|
) {
|
||||||
|
return reversePolishNotation(
|
||||||
|
tokens,
|
||||||
|
[...previousPostfix, operators[operators.length - 1]],
|
||||||
|
operators.slice(0, -1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return reversePolishNotation(tokens.slice(1), previousPostfix, [
|
||||||
|
...operators,
|
||||||
|
currentToken,
|
||||||
|
])
|
||||||
|
} else if (currentToken.value === '(') {
|
||||||
|
// push current token to both stacks as it is a legitimate operator
|
||||||
|
// but later we'll need to pop other operators off the stack until we find the matching ')'
|
||||||
|
return reversePolishNotation(
|
||||||
|
tokens.slice(1),
|
||||||
|
[...previousPostfix, currentToken],
|
||||||
|
[...operators, currentToken]
|
||||||
|
)
|
||||||
|
} else if (currentToken.value === ')') {
|
||||||
|
if (operators[operators.length - 1]?.value !== '(') {
|
||||||
|
// pop operators off the stack and pust them to postFix until we find the matching '('
|
||||||
|
return reversePolishNotation(
|
||||||
|
tokens,
|
||||||
|
[...previousPostfix, operators[operators.length - 1]],
|
||||||
|
operators.slice(0, -1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return reversePolishNotation(
|
||||||
|
tokens.slice(1),
|
||||||
|
[...previousPostfix, currentToken],
|
||||||
|
operators.slice(0, -1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (currentToken.type === 'whitespace') {
|
||||||
|
return reversePolishNotation(tokens.slice(1), previousPostfix, operators)
|
||||||
|
}
|
||||||
|
throw new Error('Unknown token')
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ParenthesisToken {
|
||||||
|
type: 'parenthesis'
|
||||||
|
value: '(' | ')'
|
||||||
|
start: number
|
||||||
|
end: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExtendedBinaryExpression extends BinaryExpression {
|
||||||
|
startExtended?: number
|
||||||
|
endExtended?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildTree = (
|
||||||
|
reversePolishNotationTokens: Token[],
|
||||||
|
stack: (
|
||||||
|
| ExtendedBinaryExpression
|
||||||
|
| Literal
|
||||||
|
| Identifier
|
||||||
|
| ParenthesisToken
|
||||||
|
)[] = []
|
||||||
|
): BinaryExpression => {
|
||||||
|
if (reversePolishNotationTokens.length === 0) {
|
||||||
|
return stack[0] as BinaryExpression
|
||||||
|
}
|
||||||
|
const currentToken = reversePolishNotationTokens[0]
|
||||||
|
if (currentToken.type === 'number' || currentToken.type === 'string') {
|
||||||
|
return buildTree(reversePolishNotationTokens.slice(1), [
|
||||||
|
...stack,
|
||||||
|
{
|
||||||
|
type: 'Literal',
|
||||||
|
value:
|
||||||
|
currentToken.type === 'number'
|
||||||
|
? Number(currentToken.value)
|
||||||
|
: currentToken.value.slice(1, -1),
|
||||||
|
raw: currentToken.value,
|
||||||
|
start: currentToken.start,
|
||||||
|
end: currentToken.end,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
} else if (currentToken.type === 'word') {
|
||||||
|
return buildTree(reversePolishNotationTokens.slice(1), [
|
||||||
|
...stack,
|
||||||
|
{
|
||||||
|
type: 'Identifier',
|
||||||
|
name: currentToken.value,
|
||||||
|
start: currentToken.start,
|
||||||
|
end: currentToken.end,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
} else if (currentToken.type === 'brace' && currentToken.value === '(') {
|
||||||
|
const paranToken: ParenthesisToken = {
|
||||||
|
type: 'parenthesis',
|
||||||
|
value: '(',
|
||||||
|
start: currentToken.start,
|
||||||
|
end: currentToken.end,
|
||||||
|
}
|
||||||
|
return buildTree(reversePolishNotationTokens.slice(1), [
|
||||||
|
...stack,
|
||||||
|
paranToken,
|
||||||
|
])
|
||||||
|
} else if (currentToken.type === 'brace' && currentToken.value === ')') {
|
||||||
|
const innerNode = stack[stack.length - 1]
|
||||||
|
|
||||||
|
const paran = stack[stack.length - 2]
|
||||||
|
|
||||||
|
const binExp: ExtendedBinaryExpression = {
|
||||||
|
...innerNode,
|
||||||
|
startExtended: paran.start,
|
||||||
|
endExtended: currentToken.end,
|
||||||
|
} as ExtendedBinaryExpression
|
||||||
|
|
||||||
|
return buildTree(reversePolishNotationTokens.slice(1), [
|
||||||
|
...stack.slice(0, -2),
|
||||||
|
binExp,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
const left = { ...stack[stack.length - 2] }
|
||||||
|
let start = left.start
|
||||||
|
if (left.type === 'BinaryExpression') {
|
||||||
|
start = left?.startExtended || left.start
|
||||||
|
delete left.startExtended
|
||||||
|
delete left.endExtended
|
||||||
|
}
|
||||||
|
|
||||||
|
const right = { ...stack[stack.length - 1] }
|
||||||
|
let end = right.end
|
||||||
|
if (right.type === 'BinaryExpression') {
|
||||||
|
end = right?.endExtended || right.end
|
||||||
|
delete right.startExtended
|
||||||
|
delete right.endExtended
|
||||||
|
}
|
||||||
|
|
||||||
|
const binExp: BinaryExpression = {
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
operator: currentToken.value,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
left: left as any,
|
||||||
|
right: right as any,
|
||||||
|
}
|
||||||
|
return buildTree(reversePolishNotationTokens.slice(1), [
|
||||||
|
...stack.slice(0, -2),
|
||||||
|
binExp,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseExpression(tokens: Token[]): BinaryExpression {
|
||||||
|
const treeWithMabyeBadTopLevelStartEnd = buildTree(
|
||||||
|
reversePolishNotation(tokens)
|
||||||
|
)
|
||||||
|
const left = treeWithMabyeBadTopLevelStartEnd?.left as any
|
||||||
|
const start = left?.startExtended || treeWithMabyeBadTopLevelStartEnd?.start
|
||||||
|
delete left.startExtended
|
||||||
|
delete left.endExtended
|
||||||
|
|
||||||
|
const right = treeWithMabyeBadTopLevelStartEnd?.right as any
|
||||||
|
const end = right?.endExtended || treeWithMabyeBadTopLevelStartEnd?.end
|
||||||
|
delete right.startExtended
|
||||||
|
delete right.endExtended
|
||||||
|
|
||||||
|
const tree: BinaryExpression = {
|
||||||
|
...treeWithMabyeBadTopLevelStartEnd,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
}
|
||||||
|
return tree
|
||||||
|
}
|
||||||
|
|
||||||
|
function _precedence(operator: Token): number {
|
||||||
|
return precedence(operator.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function precedence(operator: string): number {
|
||||||
|
// might be useful for refenecne to make it match
|
||||||
|
// another commonly used lang https://www.w3schools.com/js/js_precedence.asp
|
||||||
|
if (['+', '-'].includes(operator)) {
|
||||||
|
return 11
|
||||||
|
} else if (['*', '/', '%'].includes(operator)) {
|
||||||
|
return 12
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
@ -296,6 +296,63 @@ show(mySketch)
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
// TODO
|
||||||
|
// it('with callExpression', () => {
|
||||||
|
// const code = [
|
||||||
|
// 'const yo = (a) => a * 2',
|
||||||
|
// 'const myVar = yo(2) + 2'
|
||||||
|
// ].join('\n')
|
||||||
|
// const { root } = exe(code)
|
||||||
|
// expect(root.myVar.value).toBe(6)
|
||||||
|
// })
|
||||||
|
})
|
||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
|
|
||||||
function exe(
|
function exe(
|
||||||
|
@ -407,16 +407,22 @@ function getBinaryExpressionResult(
|
|||||||
expression: BinaryExpression,
|
expression: BinaryExpression,
|
||||||
programMemory: ProgramMemory
|
programMemory: ProgramMemory
|
||||||
) {
|
) {
|
||||||
const getVal = (part: BinaryPart) => {
|
const getVal = (part: BinaryPart): any => {
|
||||||
if (part.type === 'Literal') {
|
if (part.type === 'Literal') {
|
||||||
return part.value
|
return part.value
|
||||||
} else if (part.type === 'Identifier') {
|
} else if (part.type === 'Identifier') {
|
||||||
return programMemory.root[part.name].value
|
return programMemory.root[part.name].value
|
||||||
|
} else if (part.type === 'BinaryExpression') {
|
||||||
|
return getBinaryExpressionResult(part, programMemory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const left = getVal(expression.left)
|
const left = getVal(expression.left)
|
||||||
const right = getVal(expression.right)
|
const right = getVal(expression.right)
|
||||||
return left + right
|
if (expression.operator === '+') return left + right
|
||||||
|
if (expression.operator === '-') return left - right
|
||||||
|
if (expression.operator === '*') return left * right
|
||||||
|
if (expression.operator === '/') return left / right
|
||||||
|
if (expression.operator === '%') return left % right
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPipeExpressionResult(
|
function getPipeExpressionResult(
|
||||||
|
@ -99,6 +99,36 @@ show(mySketch)
|
|||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code.trim())
|
expect(recasted).toBe(code.trim())
|
||||||
})
|
})
|
||||||
|
it('recast nested binary expression', () => {
|
||||||
|
const code = ['const myVar = 1 + 2 * 5'].join('\n')
|
||||||
|
const { ast } = code2ast(code)
|
||||||
|
const recasted = recast(ast)
|
||||||
|
expect(recasted).toBe(code.trim())
|
||||||
|
})
|
||||||
|
it('recast nested binary expression with parans', () => {
|
||||||
|
const code = ['const myVar = 1 + (1 + 2) * 5'].join('\n')
|
||||||
|
const { ast } = code2ast(code)
|
||||||
|
const recasted = recast(ast)
|
||||||
|
expect(recasted).toBe(code.trim())
|
||||||
|
})
|
||||||
|
it('unnecessary paran wrap will be remove', () => {
|
||||||
|
const code = ['const myVar = 1 + (2 * 5)'].join('\n')
|
||||||
|
const { ast } = code2ast(code)
|
||||||
|
const recasted = recast(ast)
|
||||||
|
expect(recasted).toBe(code.replace('(', '').replace(')', ''))
|
||||||
|
})
|
||||||
|
it('complex nested binary expression', () => {
|
||||||
|
const code = ['1 * ((2 + 3) / 4 + 5)'].join('\n')
|
||||||
|
const { ast } = code2ast(code)
|
||||||
|
const recasted = recast(ast)
|
||||||
|
expect(recasted).toBe(code.trim())
|
||||||
|
})
|
||||||
|
it('multiplied paren expressions', () => {
|
||||||
|
const code = ['3 + (1 + 2) * (3 + 4)'].join('\n')
|
||||||
|
const { ast } = code2ast(code)
|
||||||
|
const recasted = recast(ast)
|
||||||
|
expect(recasted).toBe(code.trim())
|
||||||
|
})
|
||||||
it('recast array declaration', () => {
|
it('recast array declaration', () => {
|
||||||
const code = ['const three = 3', "const yo = [1, '2', three, 4 + 5]"].join(
|
const code = ['const three = 3', "const yo = [1, '2', three, 4 + 5]"].join(
|
||||||
'\n'
|
'\n'
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
ObjectExpression,
|
ObjectExpression,
|
||||||
MemberExpression,
|
MemberExpression,
|
||||||
} from './abstractSyntaxTree'
|
} from './abstractSyntaxTree'
|
||||||
|
import { precedence } from './astMathExpressions'
|
||||||
|
|
||||||
export function recast(
|
export function recast(
|
||||||
ast: Program,
|
ast: Program,
|
||||||
@ -54,9 +55,18 @@ export function recast(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function recastBinaryExpression(expression: BinaryExpression): string {
|
function recastBinaryExpression(expression: BinaryExpression): string {
|
||||||
return `${recastBinaryPart(expression.left)} ${
|
const maybeWrapIt = (a: string, doit: boolean) => (doit ? `(${a})` : a)
|
||||||
|
|
||||||
|
const shouldWrapRight =
|
||||||
|
expression.right.type === 'BinaryExpression' &&
|
||||||
|
precedence(expression.operator) > precedence(expression.right.operator)
|
||||||
|
const shouldWrapLeft =
|
||||||
|
expression.left.type === 'BinaryExpression' &&
|
||||||
|
precedence(expression.operator) > precedence(expression.left.operator)
|
||||||
|
|
||||||
|
return `${maybeWrapIt(recastBinaryPart(expression.left), shouldWrapLeft)} ${
|
||||||
expression.operator
|
expression.operator
|
||||||
} ${recastBinaryPart(expression.right)}`
|
} ${maybeWrapIt(recastBinaryPart(expression.right), shouldWrapRight)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function recastArrayExpression(
|
function recastArrayExpression(
|
||||||
@ -102,8 +112,11 @@ function recastBinaryPart(part: BinaryPart): string {
|
|||||||
return recastLiteral(part)
|
return recastLiteral(part)
|
||||||
} else if (part.type === 'Identifier') {
|
} else if (part.type === 'Identifier') {
|
||||||
return part.name
|
return part.name
|
||||||
|
} else if (part.type === 'BinaryExpression') {
|
||||||
|
return recastBinaryExpression(part)
|
||||||
}
|
}
|
||||||
throw new Error(`Cannot recast BinaryPart ${part}`)
|
return ''
|
||||||
|
// throw new Error(`Cannot recast BinaryPart ${part}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function recastLiteral(literal: Literal): string {
|
function recastLiteral(literal: Literal): string {
|
||||||
|
Reference in New Issue
Block a user