@flomsstaar/expression-tree 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -0
- package/build/example.d.ts +1 -0
- package/build/example.js +18 -0
- package/build/index.d.ts +16 -0
- package/build/index.js +16 -0
- package/build/src/enums/binary_node_operator.d.ts +7 -0
- package/build/src/enums/binary_node_operator.js +8 -0
- package/build/src/enums/node_type.d.ts +6 -0
- package/build/src/enums/node_type.js +7 -0
- package/build/src/enums/unary_node_operator.d.ts +5 -0
- package/build/src/enums/unary_node_operator.js +6 -0
- package/build/src/errors/division_by_zero_error.d.ts +3 -0
- package/build/src/errors/division_by_zero_error.js +6 -0
- package/build/src/errors/invalid_expression_error.d.ts +3 -0
- package/build/src/errors/invalid_expression_error.js +6 -0
- package/build/src/errors/invalid_number_error.d.ts +4 -0
- package/build/src/errors/invalid_number_error.js +8 -0
- package/build/src/errors/invalid_square_root_error.d.ts +3 -0
- package/build/src/errors/invalid_square_root_error.js +6 -0
- package/build/src/errors/missing_parenthese_error.d.ts +3 -0
- package/build/src/errors/missing_parenthese_error.js +6 -0
- package/build/src/errors/unknown_binary_operator_error.d.ts +3 -0
- package/build/src/errors/unknown_binary_operator_error.js +6 -0
- package/build/src/errors/unknown_node_type_error.d.ts +3 -0
- package/build/src/errors/unknown_node_type_error.js +6 -0
- package/build/src/errors/unknown_unary_operator_error.d.ts +3 -0
- package/build/src/errors/unknown_unary_operator_error.js +6 -0
- package/build/src/expression_cutter.d.ts +4 -0
- package/build/src/expression_cutter.js +7 -0
- package/build/src/expression_evaluator.d.ts +10 -0
- package/build/src/expression_evaluator.js +74 -0
- package/build/src/expression_parser.d.ts +14 -0
- package/build/src/expression_parser.js +94 -0
- package/build/src/expression_tree.d.ts +10 -0
- package/build/src/expression_tree.js +18 -0
- package/build/src/index.d.ts +1 -0
- package/build/src/index.js +1 -0
- package/build/src/models/base_node.d.ts +5 -0
- package/build/src/models/base_node.js +6 -0
- package/build/src/models/binary_node.d.ts +8 -0
- package/build/src/models/binary_node.js +13 -0
- package/build/src/models/number_node.d.ts +6 -0
- package/build/src/models/number_node.js +12 -0
- package/build/src/models/unary_node.d.ts +7 -0
- package/build/src/models/unary_node.js +11 -0
- package/build/src/render_expression.d.ts +2 -0
- package/build/src/render_expression.js +56 -0
- package/build/tests/expression_cutter.test.d.ts +1 -0
- package/build/tests/expression_cutter.test.js +9 -0
- package/build/tests/expression_evaluator.test.d.ts +1 -0
- package/build/tests/expression_evaluator.test.js +110 -0
- package/build/tests/expression_parser.test.d.ts +1 -0
- package/build/tests/expression_parser.test.js +44 -0
- package/build/tests/expression_tree.test.d.ts +1 -0
- package/build/tests/expression_tree.test.js +12 -0
- package/build/tests/inputs/expression_cutter_input.d.ts +1 -0
- package/build/tests/inputs/expression_cutter_input.js +230 -0
- package/build/tests/inputs/expression_parser_input.d.ts +2 -0
- package/build/tests/inputs/expression_parser_input.js +197 -0
- package/build/tests/inputs/expression_tree_input.d.ts +1 -0
- package/build/tests/inputs/expression_tree_input.js +44 -0
- package/build/tests/number_node.test.d.ts +1 -0
- package/build/tests/number_node.test.js +16 -0
- package/build/tests/render_expression.test.d.ts +1 -0
- package/build/tests/render_expression.test.js +85 -0
- package/build/vitest.config.d.ts +2 -0
- package/build/vitest.config.js +10 -0
- package/package.json +47 -0
- package/src/enums/binary_node_operator.ts +7 -0
- package/src/enums/node_type.ts +6 -0
- package/src/enums/unary_node_operator.ts +5 -0
- package/src/errors/division_by_zero_error.ts +6 -0
- package/src/errors/invalid_expression_error.ts +6 -0
- package/src/errors/invalid_number_error.ts +6 -0
- package/src/errors/invalid_square_root_error.ts +6 -0
- package/src/errors/missing_parenthese_error.ts +6 -0
- package/src/errors/unknown_binary_operator_error.ts +6 -0
- package/src/errors/unknown_node_type_error.ts +6 -0
- package/src/errors/unknown_unary_operator_error.ts +6 -0
- package/src/expression_cutter.ts +9 -0
- package/src/expression_evaluator.ts +76 -0
- package/src/expression_parser.ts +117 -0
- package/src/expression_tree.ts +19 -0
- package/src/models/base_node.ts +5 -0
- package/src/models/binary_node.ts +13 -0
- package/src/models/number_node.ts +12 -0
- package/src/models/unary_node.ts +12 -0
- package/src/render_expression.ts +57 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { InvalidExpressionError } from './errors/invalid_expression_error.js'
|
|
2
|
+
import { BaseNode } from './models/base_node.js'
|
|
3
|
+
import { BinaryNodeOperator } from './enums/binary_node_operator.js'
|
|
4
|
+
import { UnknownBinaryOperatorError } from './errors/unknown_binary_operator_error.js'
|
|
5
|
+
import { NumberNode } from './models/number_node.js'
|
|
6
|
+
import { UnaryNode } from './models/unary_node.js'
|
|
7
|
+
import { UnaryNodeOperator } from './enums/unary_node_operator.js'
|
|
8
|
+
import { MissingClosingParenthesisError } from './errors/missing_parenthese_error.js'
|
|
9
|
+
import { InvalidNumberError } from './errors/invalid_number_error.js'
|
|
10
|
+
import { BinaryNode } from './models/binary_node.js'
|
|
11
|
+
|
|
12
|
+
export class ExpressionParser {
|
|
13
|
+
protected tokens: string[] = []
|
|
14
|
+
protected pos: number = 0
|
|
15
|
+
|
|
16
|
+
protected peek(): string | null {
|
|
17
|
+
return this.tokens[this.pos] ?? null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
protected consume(): string {
|
|
21
|
+
if (this.pos >= this.tokens.length) {
|
|
22
|
+
throw new InvalidExpressionError()
|
|
23
|
+
}
|
|
24
|
+
return this.tokens[this.pos++]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
protected match(expected: string[]): string | null {
|
|
28
|
+
const token = this.peek()
|
|
29
|
+
if (token && expected.includes(token)) {
|
|
30
|
+
this.consume()
|
|
31
|
+
return token
|
|
32
|
+
}
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public parse(expression: string[]): BaseNode {
|
|
37
|
+
this.tokens = [...expression]
|
|
38
|
+
this.pos = 0
|
|
39
|
+
|
|
40
|
+
const node = this.parseExpression()
|
|
41
|
+
|
|
42
|
+
if (this.pos < this.tokens.length) {
|
|
43
|
+
throw new InvalidExpressionError()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return node
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protected parseExpression(): BaseNode {
|
|
50
|
+
let node = this.parseTerm()
|
|
51
|
+
|
|
52
|
+
while (this.match(['+', '-'])) {
|
|
53
|
+
const operator = this.mapRowOperator(this.tokens[this.pos - 1])
|
|
54
|
+
const right = this.parseTerm()
|
|
55
|
+
|
|
56
|
+
node = new BinaryNode(operator, node, right)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return node
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
protected parseTerm(): BaseNode {
|
|
63
|
+
let node = this.parseFactor()
|
|
64
|
+
|
|
65
|
+
while (this.match(['^', '*', '/'])) {
|
|
66
|
+
const operator = this.mapRowOperator(this.tokens[this.pos - 1])
|
|
67
|
+
const right = this.parseFactor()
|
|
68
|
+
|
|
69
|
+
node = new BinaryNode(operator, node, right)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return node
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
protected parseFactor(): BaseNode {
|
|
76
|
+
const token = this.peek()
|
|
77
|
+
|
|
78
|
+
if (token === '-') {
|
|
79
|
+
this.consume() // consume '-'
|
|
80
|
+
const value = this.parseFactor()
|
|
81
|
+
return new UnaryNode(UnaryNodeOperator.NEGATE, value)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (token === '(') {
|
|
85
|
+
this.consume() // consume '('
|
|
86
|
+
const node = this.parseExpression()
|
|
87
|
+
if (this.consume() !== ')') {
|
|
88
|
+
throw new MissingClosingParenthesisError()
|
|
89
|
+
}
|
|
90
|
+
return node
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const value = Number(this.consume())
|
|
94
|
+
if (Number.isNaN(value)) {
|
|
95
|
+
throw new InvalidNumberError(token)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return new NumberNode(value)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
protected mapRowOperator(operator: string): BinaryNodeOperator {
|
|
102
|
+
switch (operator) {
|
|
103
|
+
case '+':
|
|
104
|
+
return BinaryNodeOperator.SUM
|
|
105
|
+
case '-':
|
|
106
|
+
return BinaryNodeOperator.SUBTRACT
|
|
107
|
+
case '*':
|
|
108
|
+
return BinaryNodeOperator.MULTIPLY
|
|
109
|
+
case '/':
|
|
110
|
+
return BinaryNodeOperator.DIVIDE
|
|
111
|
+
case '^':
|
|
112
|
+
return BinaryNodeOperator.POWER
|
|
113
|
+
default:
|
|
114
|
+
throw new UnknownBinaryOperatorError(operator)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ExpressionCutter } from './expression_cutter.js'
|
|
2
|
+
import { ExpressionParser } from './expression_parser.js'
|
|
3
|
+
import { ExpressionEvaluator } from './expression_evaluator.js'
|
|
4
|
+
|
|
5
|
+
export class ExpressionTree {
|
|
6
|
+
constructor(
|
|
7
|
+
protected cutter = new ExpressionCutter(),
|
|
8
|
+
protected parser = new ExpressionParser(),
|
|
9
|
+
protected evaluator = new ExpressionEvaluator()
|
|
10
|
+
) {}
|
|
11
|
+
|
|
12
|
+
evaluate(expression: string): number {
|
|
13
|
+
const cutExpression = this.cutter.cut(expression)
|
|
14
|
+
|
|
15
|
+
const baseNode = this.parser.parse(cutExpression)
|
|
16
|
+
|
|
17
|
+
return this.evaluator.evaluate(baseNode)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BaseNode } from './base_node.js'
|
|
2
|
+
import { BinaryNodeOperator } from '../enums/binary_node_operator.js'
|
|
3
|
+
import { NodeType } from '../enums/node_type.js'
|
|
4
|
+
|
|
5
|
+
export class BinaryNode extends BaseNode {
|
|
6
|
+
constructor(
|
|
7
|
+
public operator: BinaryNodeOperator,
|
|
8
|
+
public left: BaseNode,
|
|
9
|
+
public right: BaseNode
|
|
10
|
+
) {
|
|
11
|
+
super(NodeType.BINARY)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseNode } from './base_node.js'
|
|
2
|
+
import { NodeType } from '../enums/node_type.js'
|
|
3
|
+
|
|
4
|
+
export class NumberNode extends BaseNode {
|
|
5
|
+
constructor(public readonly value: number) {
|
|
6
|
+
super(NodeType.NUMBER)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
toString(): string {
|
|
10
|
+
return this.value.toString()
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseNode } from './base_node.js'
|
|
2
|
+
import type { UnaryNodeOperator } from '../enums/unary_node_operator.js'
|
|
3
|
+
import { NodeType } from '../enums/node_type.js'
|
|
4
|
+
|
|
5
|
+
export class UnaryNode extends BaseNode {
|
|
6
|
+
constructor(
|
|
7
|
+
public operator: UnaryNodeOperator,
|
|
8
|
+
public value: BaseNode
|
|
9
|
+
) {
|
|
10
|
+
super(NodeType.UNARY)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { BaseNode } from './models/base_node.js'
|
|
2
|
+
import { NumberNode } from './models/number_node.js'
|
|
3
|
+
import { BinaryNode } from './models/binary_node.js'
|
|
4
|
+
import { BinaryNodeOperator } from './enums/binary_node_operator.js'
|
|
5
|
+
import { UnknownBinaryOperatorError } from './errors/unknown_binary_operator_error.js'
|
|
6
|
+
import { UnaryNode } from './models/unary_node.js'
|
|
7
|
+
import { UnaryNodeOperator } from './enums/unary_node_operator.js'
|
|
8
|
+
import { UnknownUnaryOperatorError } from './errors/unknown_unary_operator_error.js'
|
|
9
|
+
import { UnknownNodeTypeError } from './errors/unknown_node_type_error.js'
|
|
10
|
+
|
|
11
|
+
export function renderExpression(node: BaseNode): string {
|
|
12
|
+
if (node instanceof BinaryNode) {
|
|
13
|
+
const leftNode = renderExpression(node.left)
|
|
14
|
+
const rightNode = renderExpression(node.right)
|
|
15
|
+
const operator = {
|
|
16
|
+
[BinaryNodeOperator.SUM]: '+',
|
|
17
|
+
[BinaryNodeOperator.SUBTRACT]: '-',
|
|
18
|
+
[BinaryNodeOperator.MULTIPLY]: '*',
|
|
19
|
+
[BinaryNodeOperator.DIVIDE]: '/',
|
|
20
|
+
[BinaryNodeOperator.POWER]: '^',
|
|
21
|
+
}[node.operator]
|
|
22
|
+
|
|
23
|
+
if (!operator) {
|
|
24
|
+
throw new UnknownBinaryOperatorError(node.operator)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return `${leftNode} ${operator} ${rightNode}`
|
|
28
|
+
} else if (node instanceof UnaryNode) {
|
|
29
|
+
switch (node.operator) {
|
|
30
|
+
case UnaryNodeOperator.NEGATE: {
|
|
31
|
+
if (node.value instanceof NumberNode && node.value.value === 0) {
|
|
32
|
+
return '0'
|
|
33
|
+
} else if (node.value instanceof BinaryNode) {
|
|
34
|
+
const valueNode = renderExpression(node.value)
|
|
35
|
+
return `-(${valueNode})`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const valueNode = renderExpression(node.value)
|
|
39
|
+
return `-${valueNode}`
|
|
40
|
+
}
|
|
41
|
+
case UnaryNodeOperator.ABSOLUTE: {
|
|
42
|
+
const valueNode = renderExpression(node.value)
|
|
43
|
+
return `abs(${valueNode})`
|
|
44
|
+
}
|
|
45
|
+
case UnaryNodeOperator.SQUARE_ROOT: {
|
|
46
|
+
const valueNode = renderExpression(node.value)
|
|
47
|
+
return `sqrt(${valueNode})`
|
|
48
|
+
}
|
|
49
|
+
default:
|
|
50
|
+
throw new UnknownUnaryOperatorError(node.operator)
|
|
51
|
+
}
|
|
52
|
+
} else if (node instanceof NumberNode) {
|
|
53
|
+
return node.toString()
|
|
54
|
+
} else {
|
|
55
|
+
throw new UnknownNodeTypeError(node.type)
|
|
56
|
+
}
|
|
57
|
+
}
|