numbers-parser 4.14.3__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/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 _i in range(num_args):
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
- self.push('"' + node.AST_string_node_string + '"')
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
- # Unimplemented: BODY_ROWS
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: