numbers-parser 4.14.4__py3-none-any.whl → 4.15.1__py3-none-any.whl
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.
- numbers_parser/__init__.py +1 -0
- numbers_parser/cell.py +28 -202
- numbers_parser/constants.py +8 -4
- numbers_parser/document.py +26 -3
- numbers_parser/formula.py +423 -56
- numbers_parser/generated/TSCEArchives_pb2.py +209 -193
- numbers_parser/generated/TSSArchives_pb2.py +36 -36
- numbers_parser/generated/TSTArchives_pb2.py +328 -332
- numbers_parser/generated/mapping.py +1 -2
- numbers_parser/model.py +277 -186
- numbers_parser/numbers_uuid.py +6 -0
- numbers_parser/tokenizer.py +548 -0
- numbers_parser/xrefs.py +850 -0
- {numbers_parser-4.14.4.dist-info → numbers_parser-4.15.1.dist-info}/METADATA +5 -5
- {numbers_parser-4.14.4.dist-info → numbers_parser-4.15.1.dist-info}/RECORD +18 -16
- {numbers_parser-4.14.4.dist-info → numbers_parser-4.15.1.dist-info}/LICENSE.rst +0 -0
- {numbers_parser-4.14.4.dist-info → numbers_parser-4.15.1.dist-info}/WHEEL +0 -0
- {numbers_parser-4.14.4.dist-info → numbers_parser-4.15.1.dist-info}/entry_points.txt +0 -0
numbers_parser/formula.py
CHANGED
|
@@ -1,10 +1,65 @@
|
|
|
1
|
+
import math
|
|
1
2
|
import re
|
|
2
3
|
import warnings
|
|
3
4
|
from datetime import datetime, timedelta
|
|
4
5
|
|
|
6
|
+
from numbers_parser.constants import DECIMAL128_BIAS, OPERATOR_PRECEDENCE
|
|
5
7
|
from numbers_parser.exceptions import UnsupportedWarning
|
|
6
8
|
from numbers_parser.generated import TSCEArchives_pb2 as TSCEArchives
|
|
7
9
|
from numbers_parser.generated.functionmap import FUNCTION_MAP
|
|
10
|
+
from numbers_parser.generated.TSCEArchives_pb2 import ASTNodeArrayArchive
|
|
11
|
+
from numbers_parser.numbers_uuid import NumbersUUID
|
|
12
|
+
from numbers_parser.tokenizer import Token, Tokenizer, parse_numbers_range
|
|
13
|
+
from numbers_parser.xrefs import CellRange, CellRangeType
|
|
14
|
+
|
|
15
|
+
FUNCTION_NAME_TO_ID = {v: k for k, v in FUNCTION_MAP.items()}
|
|
16
|
+
|
|
17
|
+
OPERATOR_MAP = str.maketrans({"×": "*", "÷": "/", "≥": ">=", "≤": "<=", "≠": "<>"})
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
OPERATOR_INFIX_MAP = {
|
|
21
|
+
"=": "EQUAL_TO_NODE",
|
|
22
|
+
"+": "ADDITION_NODE",
|
|
23
|
+
"-": "SUBTRACTION_NODE",
|
|
24
|
+
"*": "MULTIPLICATION_NODE",
|
|
25
|
+
"/": "DIVISION_NODE",
|
|
26
|
+
"&": "CONCATENATION_NODE",
|
|
27
|
+
"^": "POWER_NODE",
|
|
28
|
+
"==": "EQUAL_TO_NODE",
|
|
29
|
+
"<>": "NOT_EQUAL_TO_NODE",
|
|
30
|
+
"<": "LESS_THAN_NODE",
|
|
31
|
+
">": "GREATER_THAN_NODE",
|
|
32
|
+
"<=": "LESS_THAN_OR_EQUAL_TO_NODE",
|
|
33
|
+
">=": "GREATER_THAN_OR_EQUAL_TO_NODE",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
OPERAND_ARCHIVE_MAP = {
|
|
37
|
+
Token.RANGE: "range_archive",
|
|
38
|
+
Token.NUMBER: "number_archive",
|
|
39
|
+
Token.TEXT: "text_archive",
|
|
40
|
+
Token.LOGICAL: "logical_archive",
|
|
41
|
+
Token.ERROR: "error",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# TODO: Understand what the frozen stick bits do!
|
|
45
|
+
FROZEN_STICKY_BIT_MAP = {
|
|
46
|
+
(False, False, False, False): None,
|
|
47
|
+
(False, True, False, False): (True, False, False, False),
|
|
48
|
+
(False, False, False, True): (False, False, True, False),
|
|
49
|
+
(False, True, False, True): (True, False, True, False),
|
|
50
|
+
(False, False, True, False): None,
|
|
51
|
+
(False, False, True, True): (False, False, True, False),
|
|
52
|
+
(False, True, True, False): (True, False, False, False),
|
|
53
|
+
(False, True, True, True): (True, False, True, False),
|
|
54
|
+
(True, False, False, False): None,
|
|
55
|
+
(True, True, False, False): (True, False, False, False),
|
|
56
|
+
(True, False, False, True): (False, False, True, False),
|
|
57
|
+
(True, True, False, True): (True, False, True, False),
|
|
58
|
+
(True, False, True, False): None,
|
|
59
|
+
(True, True, True, False): (True, False, False, False),
|
|
60
|
+
(True, False, True, True): (False, False, True, False),
|
|
61
|
+
(True, True, True, True): (True, False, True, False),
|
|
62
|
+
}
|
|
8
63
|
|
|
9
64
|
|
|
10
65
|
class Formula(list):
|
|
@@ -15,15 +70,373 @@ class Formula(list):
|
|
|
15
70
|
self.row = row
|
|
16
71
|
self.col = col
|
|
17
72
|
|
|
73
|
+
@classmethod
|
|
74
|
+
def from_str(cls, model, table_id, row, col, formula_str) -> int:
|
|
75
|
+
"""
|
|
76
|
+
Create a new formula by parsing a formula string and
|
|
77
|
+
return the allocated formula ID.
|
|
78
|
+
"""
|
|
79
|
+
formula = cls(model, table_id, row, col)
|
|
80
|
+
formula._tokens = cls.formula_tokens(formula_str)
|
|
81
|
+
|
|
82
|
+
model._formulas.add_table(table_id)
|
|
83
|
+
formula_attrs = {"AST_node_array": {"AST_node": []}}
|
|
84
|
+
ast_node = formula_attrs["AST_node_array"]["AST_node"]
|
|
85
|
+
|
|
86
|
+
for token in formula._tokens:
|
|
87
|
+
if token.type == Token.FUNC and token.subtype == Token.OPEN:
|
|
88
|
+
if token.value not in FUNCTION_NAME_TO_ID:
|
|
89
|
+
table_name = model.table_name(table_id)
|
|
90
|
+
cell_ref = f"{table_name}@[{row},{col}]"
|
|
91
|
+
warnings.warn(
|
|
92
|
+
f"{cell_ref}: function {token.value} is not supported.",
|
|
93
|
+
UnsupportedWarning,
|
|
94
|
+
stacklevel=2,
|
|
95
|
+
)
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
ast_node.append(
|
|
99
|
+
{
|
|
100
|
+
"AST_node_type": "FUNCTION_NODE",
|
|
101
|
+
"AST_function_node_index": FUNCTION_NAME_TO_ID[token.value],
|
|
102
|
+
"AST_function_node_numArgs": token.num_args,
|
|
103
|
+
},
|
|
104
|
+
)
|
|
105
|
+
elif token.type == Token.OPERAND:
|
|
106
|
+
func = getattr(formula, OPERAND_ARCHIVE_MAP[token.subtype])
|
|
107
|
+
ast_node.append(func(token))
|
|
108
|
+
|
|
109
|
+
elif token.type == Token.OP_IN:
|
|
110
|
+
ast_node.append({"AST_node_type": OPERATOR_INFIX_MAP[token.value]})
|
|
111
|
+
|
|
112
|
+
return model._formulas.lookup_key(
|
|
113
|
+
table_id,
|
|
114
|
+
TSCEArchives.FormulaArchive(**formula_attrs),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def add_table_xref_info(self, ref: dict[str, CellRange], node: dict) -> None:
|
|
118
|
+
if not ref.name_scope_2:
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
sheet_name = (
|
|
122
|
+
ref.name_scope_1
|
|
123
|
+
if ref.name_scope_1
|
|
124
|
+
else self._model.sheet_name(self._model.table_id_to_sheet_id(self._table_id))
|
|
125
|
+
)
|
|
126
|
+
table_uuid = self._model.table_name_to_uuid(sheet_name, ref.name_scope_2)
|
|
127
|
+
xref_archive = NumbersUUID(table_uuid).protobuf4
|
|
128
|
+
node["AST_cross_table_reference_extra_info"] = (
|
|
129
|
+
TSCEArchives.ASTNodeArrayArchive.ASTCrossTableReferenceExtraInfoArchive(
|
|
130
|
+
table_id=xref_archive,
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def _ast_sticky_bits(ref: dict[str, CellRange]) -> dict[str, str]:
|
|
136
|
+
return {
|
|
137
|
+
"begin_row_is_absolute": ref.row_start_is_abs,
|
|
138
|
+
"begin_column_is_absolute": ref.col_start_is_abs,
|
|
139
|
+
"end_row_is_absolute": ref.row_end_is_abs,
|
|
140
|
+
"end_column_is_absolute": ref.col_end_is_abs,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
def range_archive(self, token: "Token") -> dict:
|
|
144
|
+
ref = parse_numbers_range(self._model, token.value)
|
|
145
|
+
|
|
146
|
+
if ref.range_type == CellRangeType.RANGE:
|
|
147
|
+
ast_colon_tract = {
|
|
148
|
+
"preserve_rectangular": True,
|
|
149
|
+
"relative_row": [{}],
|
|
150
|
+
"relative_column": [{}],
|
|
151
|
+
"absolute_row": [{}],
|
|
152
|
+
"absolute_column": [{}],
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if not (ref.col_start_is_abs and ref.col_end_is_abs):
|
|
156
|
+
ast_colon_tract["relative_column"][0]["range_begin"] = (
|
|
157
|
+
(ref.col_end - self.col) if ref.col_start_is_abs else (ref.col_start - self.col)
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
if not (ref.col_start_is_abs) and not (ref.col_end_is_abs):
|
|
161
|
+
ast_colon_tract["relative_column"][0]["range_end"] = ref.col_end - self.col
|
|
162
|
+
|
|
163
|
+
if not (ref.row_start_is_abs and ref.row_end_is_abs):
|
|
164
|
+
ast_colon_tract["relative_row"][0]["range_begin"] = ref.row_start - self.row
|
|
165
|
+
if ref.row_start != ref.row_end:
|
|
166
|
+
ast_colon_tract["relative_row"][0]["range_end"] = ref.row_end - self.row
|
|
167
|
+
|
|
168
|
+
if ref.col_start_is_abs or ref.col_end_is_abs:
|
|
169
|
+
ast_colon_tract["absolute_column"][0]["range_begin"] = (
|
|
170
|
+
ref.col_start if ref.row_start_is_abs else ref.col_end
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
if ref.col_start_is_abs and ref.col_end_is_abs:
|
|
174
|
+
ast_colon_tract["absolute_column"][0]["range_end"] = ref.col_end
|
|
175
|
+
|
|
176
|
+
if ref.row_start_is_abs or ref.row_end_is_abs:
|
|
177
|
+
ast_colon_tract["absolute_row"][0]["range_begin"] = (
|
|
178
|
+
ref.row_start if ref.row_start_is_abs else ref.row_end_is_abs
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if ref.row_start_is_abs and ref.row_end_is_abs:
|
|
182
|
+
ast_colon_tract["absolute_row"][0]["range_end"] = ref.row_end
|
|
183
|
+
|
|
184
|
+
node = {
|
|
185
|
+
"AST_node_type": "COLON_TRACT_NODE",
|
|
186
|
+
"AST_sticky_bits": Formula._ast_sticky_bits(ref),
|
|
187
|
+
"AST_colon_tract": ast_colon_tract,
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
key = (
|
|
191
|
+
ref.col_start_is_abs,
|
|
192
|
+
ref.col_end_is_abs,
|
|
193
|
+
ref.row_start_is_abs,
|
|
194
|
+
ref.row_end_is_abs,
|
|
195
|
+
)
|
|
196
|
+
ast_frozen_sticky_bits = {}
|
|
197
|
+
if FROZEN_STICKY_BIT_MAP[key] is not None:
|
|
198
|
+
sticky_bits = FROZEN_STICKY_BIT_MAP[key]
|
|
199
|
+
ast_frozen_sticky_bits["begin_column_is_absolute"] = sticky_bits[0]
|
|
200
|
+
ast_frozen_sticky_bits["end_column_is_absolute"] = sticky_bits[1]
|
|
201
|
+
ast_frozen_sticky_bits["begin_row_is_absolute"] = sticky_bits[2]
|
|
202
|
+
ast_frozen_sticky_bits["end_row_is_absolute"] = sticky_bits[3]
|
|
203
|
+
node["AST_frozen_sticky_bits"] = ast_frozen_sticky_bits
|
|
204
|
+
|
|
205
|
+
for key in ["absolute_row", "relative_row", "absolute_column", "relative_column"]:
|
|
206
|
+
if len(ast_colon_tract[key][0].keys()) == 0:
|
|
207
|
+
del ast_colon_tract[key]
|
|
208
|
+
|
|
209
|
+
self.add_table_xref_info(ref, node)
|
|
210
|
+
|
|
211
|
+
return node
|
|
212
|
+
|
|
213
|
+
if ref.range_type == CellRangeType.ROW_RANGE:
|
|
214
|
+
row_start = ref.row_start if ref.row_start_is_abs else ref.row_start - self.row
|
|
215
|
+
row_end = ref.row_end if ref.row_end_is_abs else ref.row_end - self.row
|
|
216
|
+
|
|
217
|
+
node = {
|
|
218
|
+
"AST_node_type": "COLON_TRACT_NODE",
|
|
219
|
+
"AST_sticky_bits": Formula._ast_sticky_bits(ref),
|
|
220
|
+
"AST_colon_tract": {
|
|
221
|
+
"relative_row": [{"range_begin": row_start, "range_end": row_end}],
|
|
222
|
+
"absolute_column": [{"range_begin": 0x7FFF}],
|
|
223
|
+
"preserve_rectangular": True,
|
|
224
|
+
},
|
|
225
|
+
}
|
|
226
|
+
self.add_table_xref_info(ref, node)
|
|
227
|
+
return node
|
|
228
|
+
|
|
229
|
+
if ref.range_type == CellRangeType.COL_RANGE:
|
|
230
|
+
col_start = ref.col_start if ref.col_start_is_abs else ref.col_start - self.col
|
|
231
|
+
col_end = ref.col_end if ref.col_end_is_abs else ref.col_end - self.col
|
|
232
|
+
|
|
233
|
+
node = {
|
|
234
|
+
"AST_node_type": "COLON_TRACT_NODE",
|
|
235
|
+
"AST_sticky_bits": Formula._ast_sticky_bits(ref),
|
|
236
|
+
"AST_colon_tract": {
|
|
237
|
+
"relative_column": [{"range_begin": col_start, "range_end": col_end}],
|
|
238
|
+
"absolute_row": [{"range_begin": 2147483647}],
|
|
239
|
+
"preserve_rectangular": True,
|
|
240
|
+
},
|
|
241
|
+
}
|
|
242
|
+
self.add_table_xref_info(ref, node)
|
|
243
|
+
return node
|
|
244
|
+
|
|
245
|
+
if ref.range_type == CellRangeType.NAMED_RANGE:
|
|
246
|
+
new_ref = self._model.name_ref_cache.lookup_named_ref(self._table_id, ref)
|
|
247
|
+
if new_ref.row_start is not None:
|
|
248
|
+
row_start = (
|
|
249
|
+
new_ref.row_start if ref.row_start_is_abs else new_ref.row_start - self.row
|
|
250
|
+
)
|
|
251
|
+
row_end = new_ref.row_end if ref.row_end_is_abs else new_ref.row_end - self.row
|
|
252
|
+
node = {
|
|
253
|
+
"AST_node_type": "COLON_TRACT_NODE",
|
|
254
|
+
"AST_sticky_bits": Formula._ast_sticky_bits(ref),
|
|
255
|
+
"AST_colon_tract": {
|
|
256
|
+
"relative_row": [{"range_begin": row_start, "range_end": row_end}],
|
|
257
|
+
"absolute_column": [{"range_begin": 0x7FFF}],
|
|
258
|
+
"preserve_rectangular": True,
|
|
259
|
+
},
|
|
260
|
+
}
|
|
261
|
+
else:
|
|
262
|
+
col_start = (
|
|
263
|
+
new_ref.col_start if ref.col_start_is_abs else new_ref.col_start - self.col
|
|
264
|
+
)
|
|
265
|
+
col_end = new_ref.col_end if ref.col_end_is_abs else new_ref.col_end - self.col
|
|
266
|
+
node = {
|
|
267
|
+
"AST_node_type": "COLON_TRACT_NODE",
|
|
268
|
+
"AST_sticky_bits": Formula._ast_sticky_bits(ref),
|
|
269
|
+
"AST_colon_tract": {
|
|
270
|
+
"relative_column": [{"range_begin": col_start, "range_end": col_end}],
|
|
271
|
+
"absolute_row": [{"range_begin": 2147483647}],
|
|
272
|
+
"preserve_rectangular": True,
|
|
273
|
+
},
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
self.add_table_xref_info(ref, node)
|
|
277
|
+
return node
|
|
278
|
+
|
|
279
|
+
if ref.range_type == CellRangeType.NAMED_ROW_COLUMN:
|
|
280
|
+
new_ref = self._model.name_ref_cache.lookup_named_ref(self._table_id, ref)
|
|
281
|
+
if new_ref.row_start is not None:
|
|
282
|
+
row_start = (
|
|
283
|
+
new_ref.row_start if ref.row_start_is_abs else new_ref.row_start - self.row
|
|
284
|
+
)
|
|
285
|
+
node = {
|
|
286
|
+
"AST_node_type": "COLON_TRACT_NODE",
|
|
287
|
+
"AST_sticky_bits": Formula._ast_sticky_bits(ref),
|
|
288
|
+
"AST_colon_tract": {
|
|
289
|
+
"relative_column": [{"range_begin": row_start}],
|
|
290
|
+
"absolute_row": [{"range_begin": 2147483647}],
|
|
291
|
+
"preserve_rectangular": True,
|
|
292
|
+
},
|
|
293
|
+
}
|
|
294
|
+
else:
|
|
295
|
+
col_start = (
|
|
296
|
+
new_ref.col_start if ref.col_start_is_abs else new_ref.col_start - self.col
|
|
297
|
+
)
|
|
298
|
+
node = {
|
|
299
|
+
"AST_node_type": "COLON_TRACT_NODE",
|
|
300
|
+
"AST_sticky_bits": Formula._ast_sticky_bits(ref),
|
|
301
|
+
"AST_colon_tract": {
|
|
302
|
+
"relative_column": [{"range_begin": col_start}],
|
|
303
|
+
"absolute_row": [{"range_begin": 2147483647}],
|
|
304
|
+
"preserve_rectangular": True,
|
|
305
|
+
},
|
|
306
|
+
}
|
|
307
|
+
self.add_table_xref_info(ref, node)
|
|
308
|
+
return node
|
|
309
|
+
|
|
310
|
+
# CellRangeType.CELL
|
|
311
|
+
return {
|
|
312
|
+
"AST_node_type": "CELL_REFERENCE_NODE",
|
|
313
|
+
"AST_row": {
|
|
314
|
+
"row": ref.row_start if ref.row_start_is_abs else ref.row_start - self.row,
|
|
315
|
+
"absolute": ref.row_start_is_abs,
|
|
316
|
+
},
|
|
317
|
+
"AST_column": {
|
|
318
|
+
"column": ref.col_start if ref.col_start_is_abs else ref.col_start - self.col,
|
|
319
|
+
"absolute": ref.col_start_is_abs,
|
|
320
|
+
},
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
def number_archive(self, token: "Token") -> ASTNodeArrayArchive.ASTNodeArchive:
|
|
324
|
+
if float(token.value).is_integer():
|
|
325
|
+
return {
|
|
326
|
+
"AST_node_type": "NUMBER_NODE",
|
|
327
|
+
"AST_number_node_number": int(float(token.value)),
|
|
328
|
+
"AST_number_node_decimal_low": int(float(token.value)),
|
|
329
|
+
"AST_number_node_decimal_high": 0x3040000000000000,
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
value = token.value
|
|
333
|
+
exponent = (
|
|
334
|
+
math.floor(math.log10(math.e) * math.log(abs(float(value))))
|
|
335
|
+
if float(value) != 0.0
|
|
336
|
+
else 0
|
|
337
|
+
)
|
|
338
|
+
if "E" in value:
|
|
339
|
+
significand, exponent = value.split("E")
|
|
340
|
+
else:
|
|
341
|
+
significand = value
|
|
342
|
+
exponent = 0
|
|
343
|
+
num_dp = len(re.sub(r"0*$", "", str(significand).split(".")[1]))
|
|
344
|
+
exponent = int(exponent) - num_dp
|
|
345
|
+
decimal_low = int(float(significand) * 10**num_dp)
|
|
346
|
+
decimal_high = ((DECIMAL128_BIAS * 2) + (2 * exponent)) << 48
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
"AST_node_type": "NUMBER_NODE",
|
|
350
|
+
"AST_number_node_number": float(value),
|
|
351
|
+
"AST_number_node_decimal_low": decimal_low,
|
|
352
|
+
"AST_number_node_decimal_high": decimal_high,
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
def text_archive(self, token: "Token") -> ASTNodeArrayArchive.ASTNodeArchive:
|
|
356
|
+
# String literals from tokenizer include start and end quotes
|
|
357
|
+
value = token.value[1:-1]
|
|
358
|
+
# Numbers does not escape quotes in the AST
|
|
359
|
+
value = value.replace('""', '"')
|
|
360
|
+
return {
|
|
361
|
+
"AST_node_type": "STRING_NODE",
|
|
362
|
+
"AST_string_node_string": value,
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
def logical_archive(self, token: "Token") -> ASTNodeArrayArchive.ASTNodeArchive:
|
|
366
|
+
return {
|
|
367
|
+
"AST_node_type": "BOOLEAN_NODE",
|
|
368
|
+
"AST_boolean_node_boolean": token.value.lower() == "true",
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
def error(self, token: "Token") -> ASTNodeArrayArchive.ASTNodeArchive:
|
|
372
|
+
return {
|
|
373
|
+
"AST_node_type": "BOOLEAN_NODE",
|
|
374
|
+
"AST_boolean_node_boolean": token.value.lower() == "true",
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
@staticmethod
|
|
378
|
+
def formula_tokens(formula_str: str):
|
|
379
|
+
tok = Tokenizer(formula_str.translate(OPERATOR_MAP))
|
|
380
|
+
return Formula.rpn_tokens(tok.items)
|
|
381
|
+
|
|
382
|
+
@staticmethod
|
|
383
|
+
def rpn_tokens(tokens):
|
|
384
|
+
output = []
|
|
385
|
+
operators = []
|
|
386
|
+
|
|
387
|
+
for token in tokens:
|
|
388
|
+
if token.type in ["OPERAND", "NUMBER", "LITERAL", "TEXT", "RANGE"]:
|
|
389
|
+
output.append(token)
|
|
390
|
+
if operators and operators[-1].type == "FUNC":
|
|
391
|
+
operators[-1].num_args += 1
|
|
392
|
+
elif token.type == "FUNC" and token.subtype == "OPEN":
|
|
393
|
+
token.value = token.value[0:-1]
|
|
394
|
+
operators.append(token)
|
|
395
|
+
operators[-1].num_args = 0
|
|
396
|
+
elif token.type in ["OPERATOR-POSTFIX", "OPERATOR-PREFIX"]:
|
|
397
|
+
output.append(token)
|
|
398
|
+
elif token.type == "OPERATOR-INFIX":
|
|
399
|
+
while (
|
|
400
|
+
operators
|
|
401
|
+
and operators[-1].type == "OPERATOR-INFIX"
|
|
402
|
+
and OPERATOR_PRECEDENCE[operators[-1].value] >= OPERATOR_PRECEDENCE[token.value]
|
|
403
|
+
):
|
|
404
|
+
output.append(operators.pop())
|
|
405
|
+
operators.append(token)
|
|
406
|
+
elif token.type == "FUNC" and token.subtype == "CLOSE":
|
|
407
|
+
while operators and (
|
|
408
|
+
operators[-1].type != "FUNC" and operators[-1].subtype != "OPEN"
|
|
409
|
+
):
|
|
410
|
+
output.append(operators.pop())
|
|
411
|
+
output.append(operators.pop())
|
|
412
|
+
elif token.type == "SEP":
|
|
413
|
+
if operators and operators[-1].type != "FUNC":
|
|
414
|
+
output.append(operators.pop())
|
|
415
|
+
# Only remaining token type is PAREN
|
|
416
|
+
elif token.subtype == "OPEN":
|
|
417
|
+
operators.append(token)
|
|
418
|
+
else:
|
|
419
|
+
# Must be a CLOSE PAREN
|
|
420
|
+
while operators and operators[-1].subtype != "OPEN":
|
|
421
|
+
output.append(operators.pop())
|
|
422
|
+
operators.pop()
|
|
423
|
+
# if operators and operators[-1].type == "FUNC":
|
|
424
|
+
# output.append(operators.pop())
|
|
425
|
+
|
|
426
|
+
while operators:
|
|
427
|
+
output.append(operators.pop())
|
|
428
|
+
|
|
429
|
+
return output
|
|
430
|
+
|
|
18
431
|
def __str__(self) -> str:
|
|
19
|
-
return "".join(reversed(self._stack))
|
|
432
|
+
return "".join(reversed([str(x) for x in self._stack]))
|
|
20
433
|
|
|
21
434
|
def pop(self) -> str:
|
|
22
435
|
return self._stack.pop()
|
|
23
436
|
|
|
24
437
|
def popn(self, num_args: int) -> tuple:
|
|
25
438
|
values = ()
|
|
26
|
-
for
|
|
439
|
+
for _ in range(num_args):
|
|
27
440
|
values += (self._stack.pop(),)
|
|
28
441
|
return values
|
|
29
442
|
|
|
@@ -107,7 +520,7 @@ class Formula(list):
|
|
|
107
520
|
num_args = len(self._stack)
|
|
108
521
|
|
|
109
522
|
args = self.popn(num_args)
|
|
110
|
-
args = ",".join(reversed(args))
|
|
523
|
+
args = ",".join(reversed([str(x) for x in args]))
|
|
111
524
|
self.push(f"{func_name}({args})")
|
|
112
525
|
|
|
113
526
|
def greater_than(self, *args) -> None:
|
|
@@ -129,7 +542,7 @@ class Formula(list):
|
|
|
129
542
|
def list(self, *args) -> None:
|
|
130
543
|
node = args[2]
|
|
131
544
|
args = self.popn(node.AST_list_node_numArgs)
|
|
132
|
-
args = ",".join(reversed(args))
|
|
545
|
+
args = ",".join(reversed([str(x) for x in args]))
|
|
133
546
|
self.push(f"({args})")
|
|
134
547
|
|
|
135
548
|
def mul(self, *args) -> None:
|
|
@@ -161,7 +574,7 @@ class Formula(list):
|
|
|
161
574
|
self.push(f"{arg1}^{arg2}")
|
|
162
575
|
|
|
163
576
|
def range(self, *args) -> None:
|
|
164
|
-
arg2, arg1 = self.popn(2)
|
|
577
|
+
arg2, arg1 = [str(x) for x in self.popn(2)]
|
|
165
578
|
func_range = "(" in arg1 or "(" in arg2
|
|
166
579
|
if "::" in arg1 and not func_range:
|
|
167
580
|
# Assumes references are not cross-table
|
|
@@ -173,7 +586,10 @@ class Formula(list):
|
|
|
173
586
|
|
|
174
587
|
def string(self, *args) -> None:
|
|
175
588
|
node = args[2]
|
|
176
|
-
|
|
589
|
+
# Numbers does not escape quotes in the AST; in the app, they are
|
|
590
|
+
# doubled up just like in Excel
|
|
591
|
+
value = node.AST_string_node_string.replace('"', '""')
|
|
592
|
+
self.push(f'"{value}"')
|
|
177
593
|
|
|
178
594
|
def sub(self, *args) -> None:
|
|
179
595
|
arg2, arg1 = self.popn(2)
|
|
@@ -188,81 +604,35 @@ NODE_FUNCTION_MAP = {
|
|
|
188
604
|
"ADDITION_NODE": "add",
|
|
189
605
|
"APPEND_WHITESPACE_NODE": None,
|
|
190
606
|
"ARRAY_NODE": "array",
|
|
191
|
-
# Unimplemented: AVERAGE
|
|
192
|
-
# Unimplemented: AVERAGE_ALL
|
|
193
607
|
"BEGIN_EMBEDDED_NODE_ARRAY": None,
|
|
194
|
-
|
|
608
|
+
"BEGIN_THUNK_NODE": None,
|
|
195
609
|
"BOOLEAN_NODE": "boolean",
|
|
196
|
-
# Unimplemented: CATEGORY_REF_NODE
|
|
197
610
|
"CELL_REFERENCE_NODE": "xref",
|
|
198
|
-
# Unimplemented: CHART_GROUP_VALUE_HIERARCHY
|
|
199
611
|
"COLON_NODE": "range",
|
|
200
612
|
"COLON_NODE_WITH_UIDS": "range",
|
|
201
613
|
"COLON_TRACT_NODE": "xref",
|
|
202
|
-
# Unimplemented: COLON_WITH_UIDS_NODE
|
|
203
614
|
"CONCATENATION_NODE": "concat",
|
|
204
|
-
# Unimplemented: COUNT_ALL
|
|
205
|
-
# Unimplemented: COUNT_BLANK
|
|
206
|
-
# Unimplemented: COUNT_DUPS
|
|
207
|
-
# Unimplemented: COUNT_NO_ALL
|
|
208
|
-
# Unimplemented: COUNT_ROWS
|
|
209
|
-
# Unimplemented: COUNT_UNIQUE
|
|
210
|
-
# Unimplemented: CROSS_TABLE_CELL_REFERENCE_NODE
|
|
211
615
|
"DATE_NODE": "date",
|
|
212
616
|
"DIVISION_NODE": "div",
|
|
213
|
-
# Unimplemented: DURATION_NODE
|
|
214
617
|
"EMPTY_ARGUMENT_NODE": "empty",
|
|
215
618
|
"END_THUNK_NODE": None,
|
|
216
619
|
"EQUAL_TO_NODE": "equals",
|
|
217
620
|
"FUNCTION_NODE": "function",
|
|
218
621
|
"GREATER_THAN_NODE": "greater_than",
|
|
219
622
|
"GREATER_THAN_OR_EQUAL_TO_NODE": "greater_than_or_equal",
|
|
220
|
-
# Unimplemented: GROUP_VALUE
|
|
221
|
-
# Unimplemented: GROUP_VALUE_HIERARCHY
|
|
222
|
-
# Unimplemented: GSCE.CalculationEngineAstNodeType={ADDITION_NODE
|
|
223
|
-
# Unimplemented: INDIRECT
|
|
224
|
-
# Unimplemented: LABEL
|
|
225
623
|
"LESS_THAN_NODE": "less_than",
|
|
226
624
|
"LESS_THAN_OR_EQUAL_TO_NODE": "less_than_or_equal",
|
|
227
|
-
# Unimplemented: LINKED_CELL_REF_NODE
|
|
228
|
-
# Unimplemented: LINKED_COLUMN_REF_NODE
|
|
229
|
-
# Unimplemented: LINKED_ROW_REF_NODE
|
|
230
625
|
"LIST_NODE": "list",
|
|
231
|
-
# Unimplemented: LOCAL_CELL_REFERENCE_NODE
|
|
232
|
-
# Unimplemented: MAX
|
|
233
|
-
# Unimplemented: MEDIAN
|
|
234
|
-
# Unimplemented: MIN
|
|
235
|
-
# Unimplemented: MISSING_RUNNING_TOTAL_IN_FIELD
|
|
236
|
-
# Unimplemented: MODE
|
|
237
626
|
"MULTIPLICATION_NODE": "mul",
|
|
238
627
|
"NEGATION_NODE": "negate",
|
|
239
|
-
# Unimplemented: NONE
|
|
240
628
|
"NOT_EQUAL_TO_NODE": "not_equals",
|
|
241
629
|
"NUMBER_NODE": "number",
|
|
242
630
|
"PERCENT_NODE": "percent",
|
|
243
|
-
# Unimplemented: PLUS_SIGN_NODE
|
|
244
631
|
"POWER_NODE": "power",
|
|
245
632
|
"PREPEND_WHITESPACE_NODE": None,
|
|
246
|
-
# Unimplemented: PRODUCT
|
|
247
|
-
# Unimplemented: RANGE
|
|
248
|
-
# Unimplemented: REFERENCE_ERROR_NODE
|
|
249
|
-
# Unimplemented: REFERENCE_ERROR_WITH_UIDS_NODE
|
|
250
633
|
"STRING_NODE": "string",
|
|
251
|
-
# Unimplemented: ST_DEV
|
|
252
|
-
# Unimplemented: ST_DEV_ALL
|
|
253
|
-
# Unimplemented: ST_DEV_POP
|
|
254
|
-
# Unimplemented: ST_DEV_POP_ALL
|
|
255
634
|
"SUBTRACTION_NODE": "sub",
|
|
256
|
-
# Unimplemented: THUNK_NODE
|
|
257
635
|
"TOKEN_NODE": "boolean",
|
|
258
|
-
# Unimplemented: TOTAL
|
|
259
|
-
# Unimplemented: UID_REFERENCE_NODE
|
|
260
|
-
# Unimplemented: UNKNOWN_FUNCTION_NODE
|
|
261
|
-
# Unimplemented: VARIANCE
|
|
262
|
-
# Unimplemented: VARIANCE_ALL
|
|
263
|
-
# Unimplemented: VARIANCE_POP
|
|
264
|
-
# Unimplemented: VARIANCE_POP_ALL
|
|
265
|
-
# Unimplemented: VIEW_TRACT_REF_NODE
|
|
266
636
|
}
|
|
267
637
|
|
|
268
638
|
|
|
@@ -275,9 +645,6 @@ class TableFormulas:
|
|
|
275
645
|
for k, v in TSCEArchives._ASTNODEARRAYARCHIVE_ASTNODETYPE.values_by_number.items()
|
|
276
646
|
}
|
|
277
647
|
|
|
278
|
-
def is_formula(self, row, col):
|
|
279
|
-
return (row, col) in self._model.formula_cell_ranges(self._table_id)
|
|
280
|
-
|
|
281
648
|
def formula(self, formula_key, row, col):
|
|
282
649
|
all_formulas = self._model.formula_ast(self._table_id)
|
|
283
650
|
if formula_key not in all_formulas:
|