gnumeric 0.1.0__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.
@@ -0,0 +1,315 @@
1
+ import math
2
+ import re
3
+ from typing import Set, Union
4
+
5
+ from lark import Lark, Transformer, v_args
6
+ from lark.exceptions import UnexpectedCharacters, VisitError
7
+
8
+ from gnumeric.evaluation_errors import EvaluationError, ExpressionEvaluationException
9
+ from gnumeric.formula_functions import mathematics, statistics
10
+ from gnumeric.utils import column_from_spreadsheet, row_from_spreadsheet
11
+
12
+ function_map = {
13
+ 'abs': abs,
14
+ 'len': lambda val: len(str(val)),
15
+ **mathematics.functions,
16
+ **statistics.functions,
17
+ }
18
+
19
+
20
+ class CellReference:
21
+ def __init__(self, formula_cell, ss_row, ss_col, sheet, row_is_fixed, col_is_fixed):
22
+ self.formula_cell = formula_cell
23
+ self.ss_row = ss_row
24
+ self.ss_col = ss_col
25
+ self.sheet = sheet
26
+ self.row_is_fixed = row_is_fixed
27
+ self.col_is_fixed = col_is_fixed
28
+
29
+ @property
30
+ def cell(self):
31
+ try:
32
+ sheet = (
33
+ self.formula_cell.worksheet
34
+ if self.sheet is None
35
+ else self.formula_cell.worksheet.workbook.get_sheet_by_name(self.sheet)
36
+ )
37
+ except KeyError:
38
+ raise ExpressionEvaluationException(EvaluationError.REF)
39
+
40
+ offset_row, offset_col = self.formula_cell.value.reference_coordinate_offset
41
+
42
+ col = column_from_spreadsheet(self.ss_col)
43
+ row = row_from_spreadsheet(self.ss_row)
44
+
45
+ if not self.col_is_fixed:
46
+ col += offset_col
47
+ if not self.row_is_fixed:
48
+ row += offset_row
49
+
50
+ return sheet.cell(row, col, create=True)
51
+
52
+ @classmethod
53
+ def create_from_cell_reference(cls, formula_cell, sheet, ss_col, ss_row):
54
+ col_fixed = ss_col.startswith('$')
55
+ row_fixed = ss_row.startswith('$')
56
+ return cls(
57
+ formula_cell,
58
+ ss_row,
59
+ ss_col,
60
+ sheet,
61
+ row_is_fixed=row_fixed,
62
+ col_is_fixed=col_fixed,
63
+ )
64
+
65
+
66
+ _grammar = f"""
67
+ ?start: "=" root
68
+ | "+" root
69
+ ?root: logical_stmt
70
+ ?logical_stmt: text_stmt
71
+ | logical_stmt logical_op text_stmt -> logical
72
+ ?text_stmt: sum
73
+ | text_stmt "&" sum -> concat
74
+ ?sum: product
75
+ | sum /[+-]/ product -> arithmetic
76
+ ?product: exponentiation
77
+ | product /[*\\/]/ exponentiation -> arithmetic
78
+ ?exponentiation: atom
79
+ | atom /\\^/ exponentiation -> arithmetic
80
+
81
+ ?function_arguments: function_argument ( "," function_argument )*
82
+ ?function_argument: root
83
+ | cell_range
84
+ ?cell_range: sheet_cell_reference ":" cell_reference
85
+
86
+ ?atom: NUMBER -> number
87
+ | "#REF!" -> error_ref
88
+ | ATOMIC_STR -> atomic_string
89
+ | FUNC_NAME "(" function_arguments ")" -> function
90
+ | FUNC_NAME "(" ")" -> function
91
+ | string
92
+ | "(" root ")"
93
+ // | sheet_cell_reference -> cell_lookup
94
+
95
+ !logical_op: "=" | "<>" | "<" | "<=" | ">" | ">="
96
+
97
+ ?sheet_cell_reference: (SHEETNAME "!")? cell_reference -> sheet_cell_ref
98
+ ?cell_reference: "$"? COLUMN "$"? ROW -> cell_ref
99
+ COLUMN: LETTER~1..3
100
+ ROW: DIGIT~1..5
101
+ SHEETNAME: LETTER+
102
+
103
+ string : ESCAPED_STRING
104
+
105
+ // FUNC_NAME: {' | '.join(f'"{f}"i' for f in function_map.keys())}
106
+ FUNC_NAME: LETTER ("_"|LETTER|DIGIT|".")*
107
+ ATOMIC_STR: (LETTER | "$" | "'") ("_"|LETTER|DIGIT|"."|"!"|"$"|"'"|" ")*
108
+
109
+ %import common.DIGIT
110
+ %import common.SIGNED_NUMBER -> NUMBER
111
+ %import common.LETTER
112
+ %import common.ESCAPED_STRING
113
+ %import common.WS_INLINE
114
+ %ignore WS_INLINE
115
+ """
116
+
117
+
118
+ def to_str(value) -> str:
119
+ if isinstance(value, float) and value.is_integer():
120
+ value = int(value)
121
+ elif isinstance(value, bool):
122
+ value = 'TRUE' if value else 'FALSE'
123
+ return str(value)
124
+
125
+
126
+ @v_args(inline=True)
127
+ class ExpressionEvaluator(Transformer):
128
+ def __init__(self, cell):
129
+ self._cell = cell
130
+ self.referenced_cells = set()
131
+
132
+ def number(self, val):
133
+ try:
134
+ return int(val)
135
+ except ValueError:
136
+ return float(val)
137
+
138
+ def string(self, a):
139
+ return a.value[1:-1]
140
+
141
+ def error_ref(self):
142
+ raise ExpressionEvaluationException(EvaluationError.REF)
143
+
144
+ def atomic_string(self, name):
145
+ if name == 'TRUE':
146
+ return True
147
+ elif name == 'FALSE':
148
+ return False
149
+ else:
150
+ res = re.match(
151
+ r"('?[A-Za-z0-9_ ]+'?!)?(\$?[A-Za-z]{1,3})(\$?\d{1,5})", name
152
+ )
153
+ if res:
154
+ sheet, col, row = res.groups()
155
+ sheet = sheet[:-1] if sheet is not None else None
156
+
157
+ if sheet is not None and sheet.startswith("'") and sheet.endswith("'"):
158
+ sheet = sheet[1:-1]
159
+
160
+ cell_value = self.cell_lookup((sheet, col, row))
161
+ if cell_value is not None and not isinstance(
162
+ cell_value, (int, float, str)
163
+ ):
164
+ cell_value = cell_value.value
165
+ return cell_value
166
+
167
+ raise ExpressionEvaluationException(EvaluationError.NAME)
168
+
169
+ def logical_op(self, op):
170
+ return op.value
171
+
172
+ def logical(self, a, op, b):
173
+ if isinstance(a, str):
174
+ a = str(a).lower()
175
+ if isinstance(b, str):
176
+ b = str(b).lower()
177
+
178
+ # Numbers are always less than strings and strings always less
179
+ # than bools, so to simplify the logic below, just use different
180
+ # values for a and b if there's a type mismatch
181
+ a_type = float if isinstance(a, int) and not isinstance(a, bool) else type(a)
182
+ b_type = float if isinstance(b, int) and not isinstance(b, bool) else type(b)
183
+ if a_type != b_type:
184
+ type_val_mapper = {float: 0, str: 50, bool: 100}
185
+ a = type_val_mapper[a_type]
186
+ b = type_val_mapper[b_type]
187
+
188
+ if op == '=':
189
+ return a == b
190
+ elif op == '<>':
191
+ return a != b
192
+ elif op == '<':
193
+ return a < b
194
+ elif op == '<=':
195
+ return a <= b
196
+ elif op == '>':
197
+ return a > b
198
+ elif op == '>=':
199
+ return a >= b
200
+ else:
201
+ raise ValueError(f'Unrecognized logical operator: {op}')
202
+
203
+ def arithmetic(self, a, op, b):
204
+ if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
205
+ raise ExpressionEvaluationException(EvaluationError.VALUE)
206
+
207
+ if op == '+':
208
+ return a + b
209
+ elif op == '-':
210
+ return a - b
211
+ elif op == '*':
212
+ return a * b
213
+ elif op == '/':
214
+ return a / b
215
+ elif op == '^':
216
+ try:
217
+ return math.pow(a, b)
218
+ except OverflowError:
219
+ raise ExpressionEvaluationException(EvaluationError.NUM)
220
+
221
+ def cell_ref(self, *ref) -> CellReference:
222
+ return CellReference.create_from_cell_reference(
223
+ self._cell, None, ref[0].value, ref[1].value
224
+ )
225
+
226
+ def sheet_cell_ref(self, *ref) -> CellReference:
227
+ if len(ref) > 1:
228
+ ref[1].sheet = ref[0].value
229
+ ref = ref[1]
230
+ else:
231
+ ref = ref[0]
232
+ return ref
233
+
234
+ def cell_range(self, start, end):
235
+ start_cell = start.cell
236
+ end_cell = end.cell
237
+ cells = start_cell.worksheet.get_cell_collection(
238
+ start_cell, end_cell, include_empty=True, create_cells=True
239
+ )
240
+ self.referenced_cells.update(cells)
241
+ return cells
242
+
243
+ def cell_lookup(self, ref):
244
+ cell_ref = CellReference.create_from_cell_reference(
245
+ self._cell, ref[0], ref[1], ref[2]
246
+ )
247
+ cell = cell_ref.cell
248
+ self.referenced_cells.add(cell)
249
+ if cell is None:
250
+ return 0
251
+ return cell.result
252
+
253
+ def concat(self, a, b):
254
+ a = to_str(a)
255
+ b = to_str(b)
256
+ return a + b
257
+
258
+ def function(self, name, *args):
259
+ try:
260
+ return function_map[name.lower()](*args)
261
+ except KeyError as ex:
262
+ raise ExpressionEvaluationException(EvaluationError.NAME) from ex
263
+ except TypeError as ex:
264
+ msg = str(ex)
265
+ if msg.startswith('bad operand type'):
266
+ raise ExpressionEvaluationException(EvaluationError.VALUE) from ex
267
+ elif 'takes exactly' in str(ex):
268
+ raise ExpressionEvaluationException(EvaluationError.NA) from ex
269
+ print(type(ex), ex)
270
+ raise ex
271
+
272
+
273
+ _parser = Lark(_grammar, start='start', parser='earley')
274
+
275
+
276
+ def parse(expression: str):
277
+ try:
278
+ tree = _parser.parse(expression)
279
+ except UnexpectedCharacters:
280
+ return EvaluationError.VALUE
281
+
282
+ # print(tree.pretty())
283
+ return tree
284
+
285
+
286
+ def _full_evaluation(expression: str, cell):
287
+ tree = parse(expression)
288
+
289
+ if isinstance(tree, EvaluationError):
290
+ result, referenced_cells = tree, set()
291
+ else:
292
+ evaluator = ExpressionEvaluator(cell)
293
+ try:
294
+ result = evaluator.transform(tree)
295
+ result, referenced_cells = result, evaluator.referenced_cells
296
+ except VisitError as ex:
297
+ if isinstance(ex.orig_exc, ZeroDivisionError):
298
+ result, referenced_cells = EvaluationError.DIV0, set()
299
+ elif isinstance(ex.orig_exc, ExpressionEvaluationException):
300
+ result, referenced_cells = ex.orig_exc.error, set()
301
+ else:
302
+ print(type(ex.orig_exc), ex.orig_exc)
303
+ raise ex.orig_exc
304
+
305
+ return result, referenced_cells
306
+
307
+
308
+ def evaluate(expression: str, cell) -> Union[str, int, float, EvaluationError]:
309
+ result, _ = _full_evaluation(expression, cell)
310
+ return result
311
+
312
+
313
+ def get_referenced_cells(expression: str, cell) -> Set:
314
+ _, references = _full_evaluation(expression, cell)
315
+ return references
File without changes
@@ -0,0 +1,21 @@
1
+ from gnumeric.evaluation_errors import EvaluationError, ExpressionEvaluationException
2
+
3
+
4
+ def flatten_just_type(values, types):
5
+ keep_values = []
6
+ for v in values:
7
+ if isinstance(v, EvaluationError):
8
+ raise ExpressionEvaluationException(v)
9
+ elif isinstance(v, types):
10
+ keep_values.append(v)
11
+ elif isinstance(v, list):
12
+ keep_values.extend(flatten_just_type(v, types))
13
+ elif hasattr(v, 'get_value'):
14
+ cell_value = v.get_value(compute_expression=True)
15
+ keep_values.extend(flatten_just_type([cell_value], types))
16
+
17
+ return keep_values
18
+
19
+
20
+ def get_just_numeric(values):
21
+ return flatten_just_type(values, (int, float))
@@ -0,0 +1,21 @@
1
+ import functools
2
+
3
+ from gnumeric.formula_functions.argument_helpers import get_just_numeric
4
+
5
+
6
+ def gnm_product(*values):
7
+ values = get_just_numeric(values)
8
+ if len(values) == 0:
9
+ return 0
10
+ else:
11
+ return functools.reduce(lambda x, y: x * y, values)
12
+
13
+
14
+ def gnm_sum(*values):
15
+ return sum(get_just_numeric(values))
16
+
17
+
18
+ local_objects = locals().copy()
19
+ functions = {
20
+ name[4:]: obj for name, obj in local_objects.items() if name.startswith('gnm_')
21
+ }
@@ -0,0 +1,23 @@
1
+ from gnumeric.formula_functions.argument_helpers import get_just_numeric
2
+
3
+
4
+ def gnm_max(values):
5
+ values = get_just_numeric(values)
6
+ if len(values) == 0:
7
+ return 0
8
+ else:
9
+ return max(values)
10
+
11
+
12
+ def gnm_min(values):
13
+ values = get_just_numeric(values)
14
+ if len(values) == 0:
15
+ return 0
16
+ else:
17
+ return min(values)
18
+
19
+
20
+ local_objects = locals().copy()
21
+ functions = {
22
+ name[4:]: obj for name, obj in local_objects.items() if name.startswith('gnm_')
23
+ }