diff --git a/NEWS b/NEWS index ecf2df7..eb1b2bc 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,7 @@ o Comparison operators return bool rather than int (Note this can be a breaking in a numerical computation.) o Foreground and background color style attributes for cells. o Variable row height for cells. +o Addition of an if() conditional operator. o Addition of a find() macro to search for a cell satisfying a condition. o Addition of an is(VALUE, TYPE, TYPE,...) predicate o Addition of functions for unit displacements in the six cardinal directions diff --git a/doc/teapot.lyx b/doc/teapot.lyx index fab9eb1..f9134e2 100644 --- a/doc/teapot.lyx +++ b/doc/teapot.lyx @@ -3916,11 +3916,17 @@ underline \end_layout \begin_layout Subsubsection +\begin_inset CommandInset label +LatexCommand label +name "subsec:Shadowed" + +\end_inset + Shadowed \end_layout \begin_layout Standard -This attribute is a s simple true-false flag set with shadowed(), defaulting +This attribute is a simple true-false flag set with shadowed(), defaulting to false. When true, it means that the left neighbour cell will additionally use the display room of this cell (and all following shadowed cells to the @@ -5791,7 +5797,7 @@ or \series default \emph default -y evaluates to the logical disjunction of boolean values +y evaluates to the logical disjunction of Boolean values \emph on x \emph default @@ -5820,6 +5826,25 @@ x y \emph default otherwise. + Note the rules for these Boolean operators means they can also sometimes + be used for conditional-like behavior: Suppose you want to use the value + of the cell labeled +\family sans +OPTION +\family default + if it is non-empty and non-zero, and the value of the cell labeled +\family sans +DEFAULT +\family default + otherwise. + Since the empty and zero values are considered false, you can achieve this + with: +\family sans + +\begin_inset Newline newline +\end_inset + +@(OPTION) or @(DEFAULT) \end_layout \begin_layout Description @@ -6814,13 +6839,14 @@ $ \series bold X \series default -(SRC,REF,1,1,1) +(SRC,REF,fix,fix,fix) \family default is identical to \family sans @(SRC) \family default -, but you should certainly prefer the latter for clarity of expression. +, but you should certainly prefer the latter for clarity of expression when + that's what you mean. \begin_inset Newline newline \end_inset @@ -8251,6 +8277,102 @@ hexact used as a keyword to the string() function; listed here to record that this identifier may not be used as a cell label. \end_layout +\begin_layout Description +if +\series medium +( +\emph on +condition +\emph default +[, +\emph on + +\begin_inset space ~ +\end_inset + +then_expr +\emph default +[ +\emph on +, +\begin_inset space ~ +\end_inset + +else_expr +\emph default +]]) Typical conditional expression. + First the +\series default +\emph on +condition +\emph default + is evaluated. + If its value is falsy, then the value of the +\emph on +else_expr +\emph default + is returned if it is present, or else the empty value is returned. + (If you just want to return the value of the +\emph on +condition +\emph default + when it corresponds to boolean false, use +\begin_inset Quotes eld +\end_inset + + +\emph on +condition +\emph default + and +\emph on +then_expr +\emph default + +\begin_inset Quotes erd +\end_inset + + instead.) If the value of +\emph on +condition +\emph default + is truthy, then the value of the +\emph on +then_expr +\emph default + is returned if it is present, otherwise the value of the +\emph on +condition +\emph default + is returned. + Note that if() is short-circuiting in the sense that the +\emph on +condition +\emph default + is always evaluated but at most one of +\emph on +then_expr +\emph default + and +\emph on +else_expr +\emph default + is (note, if evaluating +\emph on +condition +\emph default + results in an error, then this expression produces an error without evaluating + either +\emph on +then_expr +\emph default + or +\emph on +else_expr +\emph default +). +\end_layout + \begin_layout Description \series medium @@ -8262,7 +8384,7 @@ int \series default int \series medium -[([int|float|string|empty +[([int|boolean|float|string|empty \emph on \begin_inset space ~ @@ -8284,14 +8406,14 @@ converts to an \emph on \emph default -integer the given integer, float, string, or empty value +integer the given integer, boolean, float, string, or empty value \emph on x. \emph default -(The latter converts to 0.) The optional second argument must be the name - of one of the functions that produces a floating point integral value from - a float, i.e., +(The latter converts to 0, and Boolean true converts to 1 and false to 0.) + The optional second argument must be the name of one of the functions that + produces a floating point integral value from a float, i.e., \family sans \series bold ceil @@ -10208,13 +10330,26 @@ If your machine uses binary floating point arithmetic, and chances are that \end_layout \begin_layout Quote + +\family sans 0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1 \end_layout \begin_layout Standard -You expect to see 1.0 as result, and indeed that is what you get. - Now you compare this result to the constant 1.0, but surprisingly for many - users, the result is 0. +You expect to see +\family sans +1.0 +\family default + as result, and indeed that is what you get. + Now you compare this result to the constant +\family sans +1.0 +\family default +, but surprisingly for many users, the result is +\family sans +false +\family default +. Appearantly, 1.0 is unequal 1.0 for \noun on teapot @@ -10275,90 +10410,179 @@ teapot has no way to hide cells, but you have three dimensions. Just use one or more layers for such cells and give each cell a label in order to reference and find it easily. - +\end_layout + +\begin_layout Standard +If you're really wedded to cells that calculate but don't appear on the + display, you can use such hacks as +\begin_inset Quotes eld +\end_inset + +shadowing +\begin_inset Quotes erd +\end_inset + + a cell with computations in it (see subsection +\begin_inset CommandInset ref +LatexCommand ref +reference "subsec:Shadowed" +plural "false" +caps "false" +noprefix "false" + +\end_inset + +) or making its foreground and background the same color, etc. \end_layout \begin_layout Subsection -Why is there no conditional evaluation? +Can you share a trick for multi-way dispatch? \end_layout \begin_layout Standard -There is no special operator or function for conditional evaluation. - I could add one easily, but then next someone would ask for loops and someone - else for user-defined functions, variables and so on. - If you need a programming language, you know where to find it. - -\end_layout - -\begin_layout Standard -But don't worry. - The answer is, that conditional evaluation comes for free with +By now, \noun on teapot \noun default -'s orthogonal cell addressing. - As an example, depending on the cell labelled -\family typewriter -X + has a conventional +\family sans +if() \family default - being negative or not, you want the result to be the string -\family typewriter -"BAD + operator for convenience when you want to choose between two alternatives + based on a single Boolean condition. + However, a methodology used to provide conditional behavior before +\family sans +if() \family default - or + was implemented is still worth knowing about, because it can actually provide + more flexibility and power, particularly in providing for multi-way dispatch. +\end_layout + +\begin_layout Standard +For example, suppose that you want to produce one of the strings +\begin_inset Quotes eld +\end_inset + + \family typewriter -"GOOD" +NONE +\family default + +\begin_inset Quotes erd +\end_inset + +, +\begin_inset Quotes eld +\end_inset + + +\family typewriter +ONE +\family default + +\begin_inset Quotes erd +\end_inset + +, or +\begin_inset Quotes eld +\end_inset + + +\family typewriter +MANY +\family default + +\begin_inset Quotes erd +\end_inset + + depending on whether the cell labeled +\family typewriter +INPUT +\family default + has a value that is zero, one, or greater than one, respectively. + Here's one convenient way to arrange that. + In some scratch area, create a cell labeled +\family typewriter +OUTPUT +\family default + that contains the string +\family typewriter + +\begin_inset Quotes erd +\end_inset + +NONE +\begin_inset Quotes erd +\end_inset + + +\family default + with the two successive cells to the right containing +\family typewriter + +\begin_inset Quotes eld +\end_inset + +ONE +\begin_inset Quotes erd +\end_inset + + +\family default + and +\family typewriter + +\begin_inset Quotes erd +\end_inset + +MANY +\begin_inset Quotes erd +\end_inset + + \family default . - This is a solution: + Then the expression \end_layout \begin_layout Quote -eval(BAD + &((@(X)>=0),0,0)) + +\family sans +@(OUTPUT, min(@(INPUT), 2)) \end_layout \begin_layout Standard -Note this is making use of the fact that you can add locations in the natural - way. - The cell labelled -\family typewriter -BAD +does the trick: the (optional) second argument of the fetch function +\family sans +@() \family default - contains the string + adds to the X-coordinate of the \family typewriter -"BAD" +OUTPUT \family default -, its right neighbour contains the string + location, and the +\family sans +min() +\family default + makes sure the amount being added to that coordinate is no more than two. + (Note that for this to work exactly as shown assumes that the value in + the \family typewriter -"GOOD" +INPUT +\family default + cell is always a non-negative integer. + You should relatively easily be able to enhance the given expression to + handle other possibilities for +\family typewriter +INPUT +\family default +, for example if it might be negative or have a floating-point value.) If + you want to use Boolean conditions on input variables in a similar way, + you can convert them to integers with +\family sans +int() \family default . - If you have nested conditions, you could weight them with 1, 2, 4 and so - on to address a bigger range of cells. - Alternatively, you could make use of all three dimensions for nested conditions. -\end_layout - -\begin_layout Standard -Sometimes you can also get conditional-like behavior using the logical expressio -ns. - Suppose you want to use the value of the cell labeled -\family sans -OPTION -\family default - if it is non-empty and non-zero, and the value of the cell labeled -\family sans -DEFAULT -\family default - otherwise. - Since the empty and zero values are considered false, but the logical operators - short-circuit and preserve the original values of their arguments, you - can achieve this with: -\end_layout - -\begin_layout Standard - -\family sans -@(OPTION) or @(DEFAULT) \end_layout \begin_layout Subsection @@ -10399,8 +10623,16 @@ absolute \family sans R(,-1)+1 \family default - or up()+1 to add one to the value of the cell which is one up from the - current cell (all of these expressions work). + or +\family sans +R(up)+1 +\family default + or +\family sans +up()+1 +\family default + to add one to the value of the cell which is up one position from the current + cell (all of these expressions work). Then you can fill that expression downwards and get your column of consecutive numbers. \end_layout @@ -10413,9 +10645,9 @@ But these sorts of relative expressions only keep working if the cells move \family sans R(,-1) \family default -) and you insert another row in between them, your references will be all +) and you insert another row in between them, your references will all be messed up. - There is value to + Thus, there is value to \begin_inset Quotes eld \end_inset @@ -10432,11 +10664,13 @@ referring to what you want. \begin_inset Quotes erd \end_inset - + Note that labeled cells handle some aspects of this desired behavior, because + the label goes with the cell when it is moved in the spreadsheet \end_layout \begin_layout Standard -To provide for this need, +However, to provide for the cases when just labels by themselves are not + enough, \noun on teapot \noun default @@ -10541,7 +10775,11 @@ X(SRC,REF,fix,fix,fix) \family sans @(SRC) \family default -, but the intent of the latter is much clearer. +, although of course if that's all you want, there's no need to use +\family sans +X() +\family default +. \end_layout \begin_layout Standard @@ -10559,15 +10797,15 @@ do the right thing \end_inset as you copy and move either that formula or the referred-to data? The response - to this is that in a typical spreadsheet, there are only a small number - of fundamental references, and all other references derive from them in - this way. + to this criticism is that in a typical spreadsheet, there are only a small + number of fundamental references, and all other references derive from + them in a natural way. So you generally only need a few labels, and by taking just a little extra time to apply those labels and refer to them in initial formulas, you are making the semantics of your references much clearer and in essence documenting them within your spreadsheet. - This modicum of extra effort will therefore be repaid in an easier-to-use, - easier-to-understand, and easier-to-maintain and update spreadsheet. + This modicum of extra effort will be repaid by a spreadsheet that is easier + to use, understand, maintain, and update. \end_layout \end_body diff --git a/src/common/func.c b/src/common/func.c index 11653f0..a9e4d9a 100644 --- a/src/common/func.c +++ b/src/common/func.c @@ -684,6 +684,39 @@ static Token blop_macro(FunctionIdentifier self, int argc, const Token argv[]) } /*}}}*/ +/* if_macro -- traditional if-then-else expression */ /*{{{*/ +static Token if_macro(FunctionIdentifier self, int argc, const Token argv[]) { + assert(self == FUNC_IF); + if (argc < 1 || argc > 3) { + Token error; + const char *usage = _("Usage: if(expr[, true_expr[, false_expr]])"); + return duperror(&error, usage); + } + Token tcond = evaltoken(argv[0], FULL); + Token cond = tbool(tcond); + if (cond.type == EEK) return cond; + assert(cond.type == BOOL); // Since it's bool no need to worry about freeing + if (cond.u.bl) { + /* Condition is true. Return value of second argument or of first + argument if there is no second argument */ + if (argc >= 2) { + tfree(&tcond); + return evaltoken(argv[1], FULL); + } + return tcond; + } + /* Condition is false. Return value of third argument or empty if there is + none. + */ + tfree(&tcond); + Token result; + result.type = EMPTY; + if (argc >= 3) { + result = evaltoken(argv[2], FULL); + } + return result; +} + /* self function, typically used for keywords */ /*{{{*/ static Token self_func(FunctionIdentifier self, int argc, const Token argv[]) { @@ -1782,6 +1815,7 @@ Tfunc tfunc[]= /* Boolean functions */ [FUNC_AND] = { "and", blop_macro, INFIX_BOOL, MACRO, 0 }, [FUNC_FALSE] = { "false", blcnst_func, PREFIX_FUNC, FUNCT, 0 }, + [FUNC_IF] = { "if", if_macro, PREFIX_FUNC, MACRO, 0 }, [FUNC_OR] = { "or", blop_macro, INFIX_BOOL, MACRO, 0 }, [FUNC_TRUE] = { "true", blcnst_func, PREFIX_FUNC, FUNCT, 0 }, diff --git a/src/common/func.h b/src/common/func.h index 3de1879..97eb799 100644 --- a/src/common/func.h +++ b/src/common/func.h @@ -53,6 +53,8 @@ typedef enum FUNC_COUNT, + FUNC_IF, + N_FUNCTION_IDS } FunctionIdentifier;