The sequence for LAMBDA in the RPN code is now:
[0] ocLambda, svJump
[1] ocPush, svStringName (parameter name)
[2] ocSep, svSep
(Repeat [1] and [2] for each parameter)
[3] (body code)
[4] ocClose, svSep
[5] (code after LAMBDA expression)
Jumps in the ocLambda point to [0], each [2], and [4], which means
there are two more jumps than parameters.
For example:
=LAMBDA(x;y;x+y)(3;4)
generates the following RPN code:
index opcode type (data)
[0] ocLambda svJump ({4,0,2,4,8})
[1] ocPush svStringName ("x")
[2] ocSep svSep
[3] ocPush svStringName ("y")
[4] ocSep svSep
[5] ocPush svStringName ("x")
[6] ocPush svStringName ("y")
[7] ocAdd svByte
[8] ocClose svSep
[9] ocPush svDouble (3.0)
[10] ocPush svDouble (4.0)
[11] ocCall svByte (3)
The first parameter is in [1], the second is in [3], the body is
in [5]-[7], and [9] is where the code resumes after the LAMBDA.
Change-Id: I4dc871893f47ebbd4dad9cc02ac3efe0ca6ca883
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207973
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
5.0 KiB
LAMBDA
The LAMBDA operator builds a callable function out of a Calc formula.
Using LAMBDA
-
LAMBDAexpects a list of parameter names, followed by an expression. -
All parameter names must be valid identifiers, and must not match any names already defined by Calc, nor any possible cell references. Identifiers are not case-sensitive. Identifiers must start with a letter, and may contain only letters, numbers, and underscores (
_). -
You may specify no parameters, if you want the function to operate the same way every time it is called (this may be useful if its expression includes volatile operators such as
RANDorTIME). -
The result of a
LAMBDAis a function. Don't expect that to show up in the spreadsheet in any useful way! You must call the function to use it. -
Call a
LAMBDAfunction just like you call a built-in function: put the name of the function (either a named range, a cell reference, or theLAMBDAexpression itself) into your formula before a pair of parentheses containing the arguments you want to pass to the function. -
If you are nesting one
LAMBDAinside another, you may use the same parameter name in both (if you really want - this can easily make things hard to read!). The innermostLAMBDAspecifying a parameter of a given name applies at each level of scope. This also works withLET. -
A function can call itself (this is called recursion), but you must be able to refer to it from within the function. For example, if cell
A1is aLAMBDAformula, the body of theLAMBDAcan call itself by referring toA1. You can name yourLAMBDAcell to make this easier to read. -
If you want your
LAMBDAfunction to permit optional parameters, enclose those parameter names (in the parameter list, not in the body) in brackets, like this:
=LAMBDA(name1, name2, [name3], [name4], expression)
-
ISOMITTEDcan be used within aLAMBDAbody to detect whether an optional parameter was provided or omitted. -
Optional parameters must not appear before any required parameters in the parameter list. Your parameters can appear in any order inside the body.
Examples
(This form is permitted, but not recommended, as it is difficult to read.)
A1:=LAMBDA(a,b,a+b)(3*5,7/(6+4))
A1 will show 15.7 (=(3*5)+(7/(6+4)) )
A1:=LAMBDA(a,b,a+b)
B1:=A1(3*5, 7/(6+4))
B1 will show 15.7
If MY_SUM is A1:
A1:=LAMBDA(a,b,a+b)
B1:=MY_SUM(3*5,7/(6+4))
B1 will show 15.7
If FACT is A1:
A1:=LAMBDA(n,IF(n>1,n*FACT(n-1),1))
B1:=FACT(5)
B1 will show 120 (FACT duplicates the functionality of the built-in
FACTORIAL function, but is less efficient)
How it Works
-
The
ocLambdaOpCodecorresponds to theLAMBDAoperator -
ocLambdais set up as a jumpOpCode, meaning that it has special rules for what parameters are evaluated, and when. -
The first stage of the compiler tokenizes the elements of the
LAMBDAexpression, like any other. It also looks for the lambda parameters, and interns them as identifiers. -
The second stage of the compiler sets up the jump table with the parameters and body. It also looks for any parameters between
ocTableRefOpen/ocTableRefClose(which correspond to brackets), and setsmIsOptionalfor those parameters. -
The
ocLambdais built in the compiled code as a sequence consisting of theocLambdatoken itself, then the parameter names (svStringName) separated byocSep, followed by anotherocSep, then the lambda body, withocCloseat the end. The jump table points to theocLambda, eachocSep, and theocClose, which allows the interpreter to find each parameter name (theocLambdaandocSeps except the last), the body (the lastocSep), and the position where execution continues (ocClose). -
When interpreted,
ocLambdacauses aScFormulaFunctionto be built using the contents of the jump table. At this point all parameters are counted, and that information is passed on to the next step. -
ScFormulaFunction's constructor clones the lambda body and saves the interpreter's contextual information, then locates each use of each parameter in the scope of that identifier in the body and makes note of where it is. -
When a
ScFormulaFunctionis called viaScInterpreter::ScCall, it builds a new interpreter out of the saved context, performs the replacements specified, and executes the interpreter to obtain a result. This means that theLAMBDAbody is executed in the same context that theLAMBDAappeared in. -
ocLambdais itself nonvolatile, and overrides any volatility in its body, since theLAMBDAnever produces a different callable, no matter what functions appear in the body. -
The presence of an enclosing
LAMBDAalso prevents circular references from being a problem, as the body of aLAMBDAis not executed until the cell containing it already has a value. This allows recursion. -
LAMBDAdoes not currently check for infinite recursion; avoid recursive calls that can cause such a loop.