forked from handsontable/hyperformula
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathArraySize.ts
175 lines (154 loc) · 5.8 KB
/
ArraySize.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
/**
* @license
* Copyright (c) 2024 Handsoncode. All rights reserved.
*/
import {AbsoluteCellRange} from './AbsoluteCellRange'
import {SimpleCellAddress} from './Cell'
import {Config} from './Config'
import {FunctionRegistry} from './interpreter/FunctionRegistry'
import {InterpreterState} from './interpreter/InterpreterState'
import {FunctionArgumentType} from './interpreter'
import {Ast, AstNodeType, ProcedureAst} from './parser'
export class ArraySize {
constructor(
public width: number,
public height: number,
public isRef: boolean = false,
) {}
public static error() {
return new ArraySize(1, 1, true)
}
public static scalar() {
return new ArraySize(1, 1, false)
}
isScalar(): boolean {
return (this.width === 1 && this.height === 1) || this.isRef
}
}
function arraySizeForBinaryOp(leftArraySize: ArraySize, rightArraySize: ArraySize): ArraySize {
return new ArraySize(Math.max(leftArraySize.width, rightArraySize.width), Math.max(leftArraySize.height, rightArraySize.height))
}
function arraySizeForUnaryOp(arraySize: ArraySize): ArraySize {
return new ArraySize(arraySize.width, arraySize.height)
}
export class ArraySizePredictor {
constructor(
private config: Config,
private functionRegistry: FunctionRegistry,
) {
}
public checkArraySize(ast: Ast, formulaAddress: SimpleCellAddress): ArraySize {
return this.checkArraySizeForAst(ast, {formulaAddress, arraysFlag: this.config.useArrayArithmetic})
}
public checkArraySizeForAst(ast: Ast, state: InterpreterState): ArraySize {
switch (ast.type) {
case AstNodeType.FUNCTION_CALL: {
return this.checkArraySizeForFunction(ast, state)
}
case AstNodeType.COLUMN_RANGE:
case AstNodeType.ROW_RANGE:
case AstNodeType.CELL_RANGE: {
const range = AbsoluteCellRange.fromAstOrUndef(ast, state.formulaAddress)
if (range === undefined) {
return ArraySize.error()
} else {
return new ArraySize(range.width(), range.height(), true)
}
}
case AstNodeType.ARRAY: {
const heights = []
const widths = []
for (const row of ast.args) {
const sizes = row.map(ast => this.checkArraySizeForAst(ast, state))
const h = Math.min(...sizes.map(size => size.height))
const w = sizes.reduce((total, size) => total + size.width, 0)
heights.push(h)
widths.push(w)
}
const height = heights.reduce((total, h) => total + h, 0)
const width = Math.min(...widths)
return new ArraySize(width, height)
}
case AstNodeType.STRING:
case AstNodeType.NUMBER:
return ArraySize.scalar()
case AstNodeType.CELL_REFERENCE:
return new ArraySize(1, 1, true)
case AstNodeType.DIV_OP:
case AstNodeType.CONCATENATE_OP:
case AstNodeType.EQUALS_OP:
case AstNodeType.GREATER_THAN_OP:
case AstNodeType.GREATER_THAN_OR_EQUAL_OP:
case AstNodeType.LESS_THAN_OP:
case AstNodeType.LESS_THAN_OR_EQUAL_OP:
case AstNodeType.MINUS_OP:
case AstNodeType.NOT_EQUAL_OP:
case AstNodeType.PLUS_OP:
case AstNodeType.POWER_OP:
case AstNodeType.TIMES_OP: {
const left = this.checkArraySizeForAst(ast.left, state)
const right = this.checkArraySizeForAst(ast.right, state)
if (!state.arraysFlag && (left.height > 1 || left.width > 1 || right.height > 1 || right.width > 1)) {
return ArraySize.error()
}
return arraySizeForBinaryOp(left, right)
}
case AstNodeType.MINUS_UNARY_OP:
case AstNodeType.PLUS_UNARY_OP:
case AstNodeType.PERCENT_OP: {
const val = this.checkArraySizeForAst(ast.value, state)
if (!state.arraysFlag && (val.height > 1 || val.width > 1)) {
return ArraySize.error()
}
return arraySizeForUnaryOp(val)
}
case AstNodeType.PARENTHESIS: {
return this.checkArraySizeForAst(ast.expression, state)
}
case AstNodeType.EMPTY:
return ArraySize.error()
default:
return ArraySize.error()
}
}
private checkArraySizeForFunction(ast: ProcedureAst, state: InterpreterState): ArraySize {
const pluginArraySizeFunction = this.functionRegistry.getArraySizeFunction(ast.procedureName)
if (pluginArraySizeFunction !== undefined) {
return pluginArraySizeFunction(ast, state)
}
const metadata = this.functionRegistry.getMetadata(ast.procedureName)
if (
metadata === undefined
|| metadata.expandRanges
|| !state.arraysFlag
|| metadata.vectorizationForbidden
|| metadata.parameters === undefined
) {
return new ArraySize(1, 1)
}
const subChecks = ast.args.map((arg) => this.checkArraySizeForAst(arg, new InterpreterState(state.formulaAddress, state.arraysFlag || (metadata?.arrayFunction ?? false))))
const argumentDefinitions = [...metadata.parameters]
if (
metadata.repeatLastArgs !== undefined
&& argumentDefinitions.length < subChecks.length
&& (subChecks.length - argumentDefinitions.length) % metadata.repeatLastArgs !== 0
) {
return ArraySize.error()
}
while (argumentDefinitions.length < subChecks.length) {
if (metadata.repeatLastArgs === undefined) {
return ArraySize.error()
}
argumentDefinitions.push(...argumentDefinitions.slice(argumentDefinitions.length - metadata.repeatLastArgs))
}
let maxWidth = 1
let maxHeight = 1
for (let i = 0; i < subChecks.length; i++) {
if (argumentDefinitions[i].argumentType !== FunctionArgumentType.RANGE && argumentDefinitions[i].argumentType !== FunctionArgumentType.ANY) {
maxHeight = Math.max(maxHeight, subChecks[i].height)
maxWidth = Math.max(maxWidth, subChecks[i].width)
}
}
return new ArraySize(maxWidth, maxHeight)
}
}