ssdiff: move comparison engine into its own file.
[gnumeric.git] / src / auto-format.c
blob87d50874048483e68db5b7ca56c8a28a8d359134
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * auto-format.c: Suggest formats for expressions.
5 * NOTE: If you were looking for the code to automatically put style on a
6 * region, you are in the wrong place. See the files file-autoft.c and
7 * dialogs/dialog-autoformat.c instead.
9 * Authors:
10 * Morten Welinder <terra@gnome.org>
13 #include <gnumeric-config.h>
14 #include "gnumeric.h"
15 #include "auto-format.h"
17 #include "compilation.h"
18 #include "func.h"
19 #include "cell.h"
20 #include "value.h"
21 #include "expr.h"
22 #include "expr-impl.h"
23 #include "sheet.h"
24 #include "workbook.h"
25 #include <goffice/goffice.h>
27 /* ------------------------------------------------------------------------- */
29 * An important note about correctness.
31 * For some functions it is easy to tell what correct behaviour is;
32 * if the evaluation of the percent operator yields anything but x/100
33 * that is bad.
35 * This function is not that simple.
37 * If we fail to suggest a format when one might have been deduced, that
38 * is really not a big deal. So the fact that "=date(2000,1,1)^1" is not
39 * recognised as a date bothers no-one.
41 * If we occasionally suggest a format where none is reasonable, that is
42 * not a problem either. "=pv(1,2,3,4,5)*today()" has no reasonable
43 * format, but we assign one. Tough.
45 * On the other hand, if we suggest a bad format for a function that does
46 * have a good format, this is bad. (Since the user will just select
47 * another format, it isn't critical, just bad.)
49 * Please resist the temptation of making this ridiculously smart. For
50 * example, avoid too much algebra here and don't look at actual numbers
51 * encountered. Let the evaluator do that. One reason for this is that
52 * if you are entering a range of similar data, you really want the same
53 * format. You don't want a different number of decimals for "22%" and
54 * "22.5%".
56 * (The problem here is actually more a physics problem -- what are the
57 * units -- than a math problem.)
59 /* ------------------------------------------------------------------------- */
61 #define AF_EXPLICIT ((GnmFuncFlags)(GNM_FUNC_AUTO_MASK + 1))
63 static GnmFuncFlags do_af_suggest_list (int argc,
64 GnmExprConstPtr const *argv,
65 GnmEvalPos const *epos,
66 GOFormat const **explicit);
68 struct cb_af_suggest { GnmFuncFlags typ; GOFormat const **explicit; };
70 static GnmValue *
71 cb_af_suggest (GnmCellIter const *iter, gpointer user)
73 struct cb_af_suggest *data = user;
75 *(data->explicit) = gnm_cell_get_format (iter->cell);
76 if (*(data->explicit)) {
77 data->typ = AF_EXPLICIT;
78 return VALUE_TERMINATE;
80 return NULL;
83 static gboolean
84 is_date (GnmFuncFlags typ, GOFormat const *explicit)
86 return (typ == GNM_FUNC_AUTO_DATE ||
87 (typ == AF_EXPLICIT && go_format_is_date (explicit)));
90 static GnmFuncFlags
91 do_af_suggest (GnmExpr const *expr, GnmEvalPos const *epos, GOFormat const **explicit)
93 switch (GNM_EXPR_GET_OPER (expr)) {
94 case GNM_EXPR_OP_EQUAL:
95 case GNM_EXPR_OP_GT:
96 case GNM_EXPR_OP_LT:
97 case GNM_EXPR_OP_GTE:
98 case GNM_EXPR_OP_LTE:
99 case GNM_EXPR_OP_NOT_EQUAL:
100 return GNM_FUNC_AUTO_UNITLESS; /* Close enough. */
102 case GNM_EXPR_OP_MULT:
103 /* Fall through. This isn't quite right, but good enough. */
104 case GNM_EXPR_OP_ADD: {
105 /* Return the first interesting type we see. */
106 GnmFuncFlags typ;
108 typ = do_af_suggest (expr->binary.value_a, epos, explicit);
109 if (typ != GNM_FUNC_AUTO_UNKNOWN && typ != GNM_FUNC_AUTO_UNITLESS)
110 return typ;
112 return do_af_suggest (expr->binary.value_b, epos, explicit);
115 case GNM_EXPR_OP_SUB: {
116 GnmFuncFlags typ1, typ2;
117 GOFormat const *explicit1 = NULL, *explicit2 = NULL;
119 typ1 = do_af_suggest (expr->binary.value_a, epos, &explicit1);
120 typ2 = do_af_suggest (expr->binary.value_b, epos, &explicit2);
122 if (is_date (typ1, explicit1) && is_date (typ2, explicit2))
123 return GNM_FUNC_AUTO_UNITLESS;
124 else if (typ1 != GNM_FUNC_AUTO_UNKNOWN && typ1 != GNM_FUNC_AUTO_UNITLESS) {
125 *explicit = explicit1;
126 return typ1;
127 } else {
128 *explicit = explicit2;
129 return typ2;
133 case GNM_EXPR_OP_DIV:
134 /* Check the left-hand side only. */
135 return do_af_suggest (expr->binary.value_a, epos, explicit);
137 case GNM_EXPR_OP_FUNCALL: {
138 GnmFuncFlags typ =
139 (expr->func.func->flags & GNM_FUNC_AUTO_MASK);
141 switch (typ) {
142 case GNM_FUNC_AUTO_FIRST:
143 return do_af_suggest_list (expr->func.argc,
144 expr->func.argv,
145 epos, explicit);
147 case GNM_FUNC_AUTO_SECOND:
148 return do_af_suggest_list (expr->func.argc - 1,
149 expr->func.argv + 1,
150 epos, explicit);
152 default:
153 return typ;
157 case GNM_EXPR_OP_CONSTANT: {
158 GnmValue const *v = expr->constant.value;
160 switch (v->v_any.type) {
161 case VALUE_STRING:
162 case VALUE_ERROR:
163 return GNM_FUNC_AUTO_UNKNOWN;
165 case VALUE_CELLRANGE: {
166 struct cb_af_suggest closure;
168 /* If we don't have a sheet, we cannot look up vars. */
169 if (epos->sheet == NULL)
170 return GNM_FUNC_AUTO_UNKNOWN;
172 closure.typ = GNM_FUNC_AUTO_UNKNOWN;
173 closure.explicit = explicit;
174 workbook_foreach_cell_in_range (epos, v,
175 CELL_ITER_IGNORE_BLANK,
176 &cb_af_suggest, &closure);
177 return closure.typ;
180 default:
181 return GNM_FUNC_AUTO_UNITLESS;
185 case GNM_EXPR_OP_CELLREF: {
186 Sheet const *sheet;
187 GnmCellRef const *ref;
188 GnmCell const *cell;
189 GnmCellPos pos;
191 ref = &expr->cellref.ref;
192 sheet = eval_sheet (ref->sheet, epos->sheet);
193 /* If we don't have a sheet, we cannot look up vars. */
194 if (sheet == NULL)
195 return GNM_FUNC_AUTO_UNKNOWN;
197 gnm_cellpos_init_cellref (&pos, ref, &epos->eval, sheet);
198 cell = sheet_cell_get (sheet, pos.col, pos.row);
199 if (cell == NULL)
200 return GNM_FUNC_AUTO_UNKNOWN;
202 *explicit = gnm_cell_get_format (cell);
203 return *explicit ? AF_EXPLICIT : GNM_FUNC_AUTO_UNKNOWN;
206 case GNM_EXPR_OP_PAREN:
207 case GNM_EXPR_OP_UNARY_NEG:
208 case GNM_EXPR_OP_UNARY_PLUS:
209 return do_af_suggest (expr->unary.value, epos, explicit);
211 case GNM_EXPR_OP_PERCENTAGE:
212 return GNM_FUNC_AUTO_PERCENT;
214 case GNM_EXPR_OP_EXP:
215 case GNM_EXPR_OP_CAT:
216 case GNM_EXPR_OP_NAME:
217 case GNM_EXPR_OP_ARRAY_CORNER:
218 case GNM_EXPR_OP_ARRAY_ELEM:
219 default:
220 return GNM_FUNC_AUTO_UNKNOWN;
224 static GnmFuncFlags
225 do_af_suggest_list (int argc, GnmExprConstPtr const *argv,
226 GnmEvalPos const *epos, GOFormat const **explicit)
228 int i;
230 for (i = 0; i < argc; i++) {
231 GnmFuncFlags typ = do_af_suggest (argv[i], epos, explicit);
232 if (typ != GNM_FUNC_AUTO_UNKNOWN &&
233 typ != GNM_FUNC_AUTO_UNITLESS)
234 return typ;
237 return GNM_FUNC_AUTO_UNKNOWN;
240 /* ------------------------------------------------------------------------- */
242 GNM_BEGIN_KILL_SWITCH_WARNING
244 GOFormat const *
245 gnm_auto_style_format_suggest (GnmExprTop const *texpr, GnmEvalPos const *epos)
247 GOFormat const *explicit = NULL;
249 g_return_val_if_fail (texpr != NULL, NULL);
250 g_return_val_if_fail (epos != NULL, NULL);
252 switch (do_af_suggest (texpr->expr, epos, &explicit)) {
253 case AF_EXPLICIT:
254 break;
256 case GNM_FUNC_AUTO_DATE: /* FIXME: any better idea? */
257 explicit = go_format_default_date ();
258 break;
260 case GNM_FUNC_AUTO_TIME: /* FIXME: any better idea? */
261 explicit = go_format_default_time ();
262 break;
264 case GNM_FUNC_AUTO_PERCENT: /* FIXME: any better idea? */
265 explicit = go_format_default_percentage ();
266 break;
268 case GNM_FUNC_AUTO_MONETARY: /* FIXME: any better idea? */
269 explicit = go_format_default_money ();
270 break;
272 case GNM_FUNC_AUTO_FIRST:
273 case GNM_FUNC_AUTO_SECOND:
274 g_assert_not_reached ();
276 default:
277 explicit = NULL;
280 if (explicit)
281 go_format_ref (explicit);
283 return explicit;
286 GNM_END_KILL_SWITCH_WARNING