fpspreadsheet: Implement calculation of rnp date/time and info formulas. Partial implementation of SUBSTITUTE calculation is back. Add corresponding unit tests, passed.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3262 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2014-07-01 12:55:02 +00:00
parent c6817250ed
commit 0aaff39316
5 changed files with 712 additions and 40 deletions

View File

@ -86,6 +86,21 @@ function fpsSINH (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsSQRT (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsTAN (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsTANH (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
{ Date/time functions }
function fpsDATE (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsDATEDIF (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsDATEVALUE (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsDAY (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsHOUR (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsMINUTE (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsMONTH (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsNOW (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsSECOND (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsTIME (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsTIMEVALUE (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsTODAY (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsWEEKDAY (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsYEAR (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
{ Statistical functions }
function fpsAVEDEV (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsAVERAGE (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
@ -114,13 +129,23 @@ function fpsLOWER (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsMID (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsREPLACE (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsRIGHT (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsSUBSTITUTE (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsTRIM (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsUPPER (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
{ info functions }
function fpsISERR (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsISERROR (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsISLOGICAL (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsISNA (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsISNONTEXT (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsISNUMBER (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsISTEXT (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsVALUE (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
implementation
uses
Math, lazutf8, StrUtils, fpsUtils;
Math, lazutf8, StrUtils, DateUtils, fpsUtils;
type
TBoolArray = array of boolean;
@ -222,6 +247,7 @@ begin
AValue := Arg.BoolValue;
Result := errOK;
end;
atError: Result := Arg.ErrorValue;
else Result := errWrongType;
end;
end;
@ -233,6 +259,7 @@ begin
atNumber : ANumber := Arg.NumberValue;
atString : if not TryStrToFloat(arg.StringValue, ANumber) then Result := errWrongType;
atBool : if Arg.BoolValue then ANumber := 1.0 else ANumber := 0.0;
atError : Result := Arg.ErrorValue;
end;
end;
@ -243,6 +270,7 @@ begin
AString := Arg.StringValue;
Result := errOK;
end;
atError : Result := Arg.ErrorValue;
else Result := errWrongType;
end;
end;
@ -332,6 +360,31 @@ begin
end;
end;
function PopDateValue(Args: TsArgumentStack; out ADate: TDate;
out AErrArg: TsArgument): Boolean;
var
arg: TsArgument;
begin
arg := Args.Pop;
case arg.ArgumentType of
atError, atBool, atEmpty:
begin
Result := false;
AErrArg := CreateError(errWrongType);
end;
atNumber:
begin
Result := true;
ADate := arg.NumberValue;
end;
atString:
begin
Result := TryStrToDate(arg.StringValue, ADate);
if not Result then AErrArg := CreateError(errWrongType);
end
end;
end;
{@@
Pops floating point values from the argument stack. Is called when
calculating rpn formulas.
@ -435,6 +488,32 @@ begin
end;
function PopTimeValue(Args: TsArgumentStack; out ATime: TTime;
out AErrArg: TsArgument): Boolean;
var
arg: TsArgument;
begin
arg := Args.Pop;
case arg.ArgumentType of
atError, atBool, atEmpty:
begin
Result := false;
AErrArg := CreateError(errWrongType);
end;
atNumber:
begin
Result := true;
ATime := frac(arg.NumberValue);
end;
atString:
begin
Result := TryStrToTime(arg.StringValue, ATime);
if not Result then AErrArg := CreateError(errWrongType);
end
end;
end;
{ Operations }
function fpsAdd(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
@ -759,13 +838,13 @@ begin
base := arg_base.NumberValue;
end;
end;
arg_number := Args.Pop;
if base < 0 then begin
Result := CreateError(errOverflow);
exit;
end;
arg_number := Args.Pop;
if arg_number.ArgumentType <> atNumber then begin
Result := CreateError(errWrongType);
exit;
@ -872,6 +951,159 @@ begin
end;
{ Date/time functions }
function fpsDATE(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
// DATE( year, month, day )
var
data: TFloatArray;
d: TDate;
begin
if PopFloatValues(Args, 3, data, Result) then begin
d := EncodeDate(round(data[0]), round(data[1]), round(data[2]));
Result := CreateNumber(d);
end;
end;
function fpsDATEDIF(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
{ DATEDIF( start_date, end_date, interval )
start_date <= end_date !
interval = Y - The number of complete years.
= M - The number of complete months.
= D - The number of days.
= MD - The difference between the days (months and years are ignored).
= YM - The difference between the months (days and years are ignored).
= YD - The difference between the days (years and dates are ignored). }
var
data: TStrArray;
start_date, end_date: TDate;
begin
if not PopStringValues(Args, 1, data, Result) then exit;
if not PopDateValue(Args, end_date, Result) then exit;
if not PopDateValue(Args, start_date, Result) then exit;
if end_date > start_date then
Result := CreateError(errOverflow)
else if data[0] = 'Y' then
Result := CreateNumber(YearsBetween(end_date, start_date))
else if data[0] = 'M' then
Result := CreateNumber(MonthsBetween(end_date, start_date))
else if data[0] = 'D' then
Result := CreateNumber(DaysBetween(end_date, start_date))
else
Result := CreateError(errFormulaNotSupported);
end;
function fpsDATEVALUE(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
// DATEVALUE( date ) -- date can be a string or a date/time
var
d: TDate;
begin
if PopDateValue(Args, d, Result) then
Result := CreateNumber(d);
end;
function fpsDAY(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
var
d: TDate;
begin
if PopDateValue(Args, d, Result) then
Result := CreateNumber(DayOf(d));
end;
function fpsHOUR(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
var
t: TTime;
begin
if PopTimeValue(Args, t, Result) then
Result := CreateNumber(HourOf(t));
end;
function fpsMINUTE(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
var
t: TTime;
begin
if PopTimeValue(Args, t, Result) then
Result := CreateNumber(MinuteOf(t));
end;
function fpsMONTH(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
var
d: TDate;
begin
if PopDateValue(Args, d, Result) then
Result := CreateNumber(MonthOf(d));
end;
function fpsNOW(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
// NOW()
begin
Result := CreateNumber(now);
end;
function fpsSECOND(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
var
t: TTime;
begin
if PopTimeValue(Args, t, Result) then
Result := CreateNumber(SecondOf(t));
end;
function fpsTIME(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
// TIME( hour, minute, second )
var
data: TFloatArray;
t: TTime;
begin
if PopFloatValues(Args, 3, data, Result) then begin
t := EncodeTime(round(data[0]), round(data[1]), round(data[2]), 0);
Result := CreateNumber(t);
end;
end;
function fpsTIMEVALUE(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
// TIMEVALUE( time_value )
var
t: TTime;
begin
if PopTimeValue(Args, t, Result) then
Result := CreateNumber(t);
end;
function fpsToday(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
// TODAY()
begin
Result := CreateNumber(Date());
end;
function fpsWEEKDAY(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
{ WEEKDAY( serial_number, [return_value]
return_value = 1 - Returns a number from 1 (Sunday) to 7 (Saturday).
This is the default if parameter is omitted.
= 2 - Returns a number from 1 (Monday) to 7 (Sunday).
= 3 - Returns a number from 0 (Monday) to 6 (Sunday). }
var
d: TDate;
data: TFloatArray;
n: Integer;
begin
n := 1;
if NumArgs = 2 then
if PopFloatValues(Args, 1, data, Result) then n := round(data[0])
else exit;
if PopDateValue(Args, d, Result) then
Result := CreateNumber(DayOfWeek(d));
end;
function fpsYEAR(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
var
d: TDate;
begin
if PopDateValue(Args, d, Result) then
Result := CreateNumber(YearOf(d));
end;
{ Statistical functions }
function fpsAVEDEV(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
@ -901,15 +1133,19 @@ begin
end;
function fpsCOUNT(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
{ Count non-missing arguments
{ counts the number of cells that contain numbers as well as the number of
arguments that contain numbers.
COUNT( argument1, [argument2, ... argument_n] )
}
var
n, i: Integer;
arg: TsArgument;
begin
n := 0;
for i:=1 to NumArgs do
if not Args.Pop.IsMissing then inc(n);
for i:=1 to NumArgs do begin
arg := Args.Pop;
if (not arg.IsMissing) and (arg.ArgumentType = atNumber) then inc(n);
end;
Result := CreateNumber(n);
end;
@ -1221,6 +1457,34 @@ begin
Result := CreateString(UTF8RightStr(s, count));
end;
function fpsSUBSTITUTE(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
// SUBSTITUTE( text, old_text, new_text, [nth_appearance] )
var
n: Integer;
arg: TsArgument;
data: TStrArray;
s, s_old, s_new: String;
begin
Result := CreateError(errWrongType);
n := -1;
if (NumArgs = 4) then begin
arg := Args.Pop;
if not arg.IsMissing and (arg.ArgumentType <> atNumber) then
exit;
n := round(arg.NumberValue);
end;
if PopStringValues(Args, 3, data, Result) then begin
s := data[0];
s_old := data[1];
s_new := data[2];
if n = -1 then
Result := CreateString(UTF8StringReplace(s, s_old, s_new, [rfReplaceAll]))
else
Result := CreateError(errFormulaNotSupported);
end;
end;
function fpsTRIM(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
// TRIM( text )
var
@ -1239,4 +1503,92 @@ begin
Result := CreateString(UTF8UpperCase(data[0]));
end;
{ Info functions }
function fpsISERR(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
// ISERR( value )
// If value is an error value (except #N/A), this function will return TRUE.
// Otherwise, it will return FALSE.
var
arg: TsArgument;
begin
arg := Args.Pop;
Result := CreateBool((arg.ArgumentType = atError) and (arg.ErrorValue <> errArgError));
end;
function fpsISERROR(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
// ISERROR( value )
// If value is an error value (#N/A, #VALUE!, #REF!, #DIV/0!, #NUM!, #NAME?
// or #NULL), this function will return TRUE. Otherwise, it will return FALSE.
var
arg: TsArgument;
begin
arg := Args.Pop;
Result := CreateBool((arg.ArgumentType = atError));
end;
function fpsISLOGICAL(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
// ISLOGICAL( value )
var
arg: TsArgument;
begin
arg := Args.Pop;
Result := CreateBool(arg.ArgumentType = atBool);
end;
function fpsISNA(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
// ISNA( value )
// If value is a #N/A error value , this function will return TRUE.
// Otherwise, it will return FALSE.
var
arg: TsArgument;
begin
arg := Args.Pop;
Result := CreateBool((arg.ArgumentType = atError) and (arg.ErrorValue = errArgError));
end;
function fpsISNONTEXT(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
// ISNONTEXT( value )
var
arg: TsArgument;
begin
arg := Args.Pop;
Result := CreateBool(arg.ArgumentType <> atString);
end;
function fpsISNUMBER(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
// ISNUMBER( value )
var
arg: TsArgument;
begin
arg := Args.Pop;
Result := CreateBool(arg.ArgumentType = atNumber);
end;
function fpsISTEXT(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
// ISTEXT( value )
var
arg: TsArgument;
begin
arg := Args.Pop;
Result := CreateBool(arg.ArgumentType = atString);
end;
function fpsVALUE(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
// VALUE( text )
// text is the text value to convert to a number. If text is not a number, the
// VALUE function will return #VALUE!.
var
data: TStrArray;
x: Double;
begin
if PopStringValues(Args, 1, data, Result) then
if TryStrToFloat(data[0], x) then
Result := CreateNumber(x)
else
Result := CreateError(errWrongType);
end;
end.

View File

@ -1094,8 +1094,8 @@ type
end;
const
FEProps: array[TFEKind] of TFEProp = (
{ Operands }
FEProps: array[TFEKind] of TFEProp = ( // functions marked by (*)
{ Operands } // are only partially supported
(Symbol:''; MinParams:Byte(-1); MaxParams:Byte(-1); Func:nil), // fekCell
(Symbol:''; MinParams:Byte(-1); MaxParams:Byte(-1); Func:nil), // fekCellRef
(Symbol:''; MinParams:Byte(-1); MaxParams:Byte(-1); Func:nil), // fekCellRange
@ -1149,20 +1149,20 @@ const
(Symbol:'TAN'; MinParams:1; MaxParams:1; Func:fpsTAN), // fekTAN
(Symbol:'TANH'; MinParams:1; MaxParams:1; Func:fpsTANH), // fekTANH,
{ date/time }
(Symbol:'DATE'; MinParams:3; MaxParams:3; Func:nil), // fekDATE
(Symbol:'DATEDIF'; MinParams:3; MaxParams:3; Func:nil), // fekDATEDIF
(Symbol:'DATEVALUE'; MinParams:1; MaxParams:1; Func:nil), // fekDATEVALUE
(Symbol:'DAY'; MinParams:1; MaxParams:1; Func:nil), // fekDAY
(Symbol:'HOUR'; MinParams:1; MaxParams:1; Func:nil), // fekHOUR
(Symbol:'MINUTE'; MinParams:1; MaxParams:1; Func:nil), // fekMINUTE
(Symbol:'MONTH'; MinParams:1; MaxParams:1; Func:nil), // fekMONTH
(Symbol:'NOW'; MinParams:0; MaxParams:0; Func:nil), // fekNOW
(Symbol:'SECOND'; MinParams:1; MaxParams:1; Func:nil), // fekSECOND
(Symbol:'TIME'; MinParams:3; MaxParams:3; Func:nil), // fekTIME
(Symbol:'TIMEVALUE'; MinParams:1; MaxParams:1; Func:nil), // fekTIMEVALUE
(Symbol:'TODAY'; MinParams:0; MaxParams:0; Func:nil), // fekTODAY
(Symbol:'WEEKDAY'; MinParams:1; MaxParams:2; Func:nil), // fekWEEKDAY
(Symbol:'YEAR'; MinParams:1; MaxParams:1; Func:nil), // fekYEAR
(Symbol:'DATE'; MinParams:3; MaxParams:3; Func:fpsDATE), // fekDATE
(Symbol:'DATEDIF'; MinParams:3; MaxParams:3; Func:fpsDATEDIF), // fekDATEDIF (*)
(Symbol:'DATEVALUE'; MinParams:1; MaxParams:1; Func:fpsDATEVALUE), // fekDATEVALUE
(Symbol:'DAY'; MinParams:1; MaxParams:1; Func:fpsDAY), // fekDAY
(Symbol:'HOUR'; MinParams:1; MaxParams:1; Func:fpsHOUR), // fekHOUR
(Symbol:'MINUTE'; MinParams:1; MaxParams:1; Func:fpsMINUTE), // fekMINUTE
(Symbol:'MONTH'; MinParams:1; MaxParams:1; Func:fpsMONTH), // fekMONTH
(Symbol:'NOW'; MinParams:0; MaxParams:0; Func:fpsNOW), // fekNOW
(Symbol:'SECOND'; MinParams:1; MaxParams:1; Func:fpsSECOND), // fekSECOND
(Symbol:'TIME'; MinParams:3; MaxParams:3; Func:fpsTIME), // fekTIME
(Symbol:'TIMEVALUE'; MinParams:1; MaxParams:1; Func:fpsTIMEVALUE), // fekTIMEVALUE
(Symbol:'TODAY'; MinParams:0; MaxParams:0; Func:fpsTODAY), // fekTODAY
(Symbol:'WEEKDAY'; MinParams:1; MaxParams:2; Func:fpsWEEKDAY), // fekWEEKDAY
(Symbol:'YEAR'; MinParams:1; MaxParams:1; Func:fpsYEAR), // fekYEAR
{ statistical }
(Symbol:'AVEDEV'; MinParams:1; MaxParams:30; Func:fpsAVEDEV), // fekAVEDEV
(Symbol:'AVERAGE'; MinParams:1; MaxParams:30; Func:fpsAVERAGE), // fekAVERAGE
@ -1210,7 +1210,7 @@ const
(Symbol:'PROPER'; MinParams:1; MaxParams:1; Func:nil), // fekPROPER
(Symbol:'REPLACE'; MinParams:4; MaxParams:4; Func:fpsREPLACE), // fekREPLACE
(Symbol:'RIGHT'; MinParams:1; MaxParams:2; Func:fpsRIGHT), // fekRIGHT
(Symbol:'SUBSTITUTE';MinParams:3; MaxParams:4; Func:nil), // fekSUBSTITUTE
(Symbol:'SUBSTITUTE';MinParams:3; MaxParams:4; Func:fpsSUBSTITUTE), // fekSUBSTITUTE (*)
(Symbol:'TRIM'; MinParams:1; MaxParams:1; Func:fpsTRIM), // fekTRIM
(Symbol:'UPPER'; MinParams:1; MaxParams:1; Func:fpsUPPER), // fekUPPER
{ lookup/reference }
@ -1222,15 +1222,15 @@ const
(Symbol:'CELL'; MinParams:1; MaxParams:2; Func:nil), // fekCELLINFO
(Symbol:'INFO'; MinParams:1; MaxParams:1; Func:nil), // fekINFO
(Symbol:'ISBLANK'; MinParams:1; MaxParams:1; Func:nil), // fekIsBLANK
(Symbol:'ISERR'; MinParams:1; MaxParams:1; Func:nil), // fekIsERR
(Symbol:'ISERROR'; MinParams:1; MaxParams:1; Func:nil), // fekIsERROR
(Symbol:'ISLOGICAL'; MinParams:1; MaxParams:1; Func:nil), // fekIsLOGICAL
(Symbol:'ISNA'; MinParams:1; MaxParams:1; Func:nil), // fekIsNA
(Symbol:'ISNONTEXT'; MinParams:1; MaxParams:1; Func:nil), // fekIsNONTEXT
(Symbol:'ISNUMBER'; MinParams:1; MaxParams:1; Func:nil), // fekIsNUMBER
(Symbol:'ISERR'; MinParams:1; MaxParams:1; Func:fpsISERR), // fekIsERR
(Symbol:'ISERROR'; MinParams:1; MaxParams:1; Func:fpsISERROR), // fekIsERROR
(Symbol:'ISLOGICAL'; MinParams:1; MaxParams:1; Func:fpsISLOGICAL), // fekIsLOGICAL
(Symbol:'ISNA'; MinParams:1; MaxParams:1; Func:fpsISNA), // fekIsNA
(Symbol:'ISNONTEXT'; MinParams:1; MaxParams:1; Func:fpsISNONTEXT), // fekIsNONTEXT
(Symbol:'ISNUMBER'; MinParams:1; MaxParams:1; Func:fpsISNUMBER), // fekIsNUMBER
(Symbol:'ISREF'; MinParams:1; MaxParams:1; Func:nil), // fekIsRef
(Symbol:'ISTEXT'; MinParams:1; MaxParams:1; Func:nil), // fekIsTEXT
(Symbol:'VALUE'; MinParams:1; MaxParams:1; Func:nil), // fekValue
(Symbol:'ISTEXT'; MinParams:1; MaxParams:1; Func:fpsISTEXT), // fekIsTEXT
(Symbol:'VALUE'; MinParams:1; MaxParams:1; Func:fpsVALUE), // fekValue
{ Other operations }
(Symbol:'SUM'; MinParams:1; MaxParams:1; Func:nil) // fekOpSUM (Unary sum operation). Note: CANNOT be used for summing sell contents; use fekSUM}
);
@ -1460,14 +1460,18 @@ begin
func := FEProps[fe.ElementKind].Func;
if not Assigned(func) then begin
// calculation of function not implemented
WriteErrorValue(ACell, errFormulaNotSupported);
exit;
end;
if args.Count < fe.ParamsNum then begin
// not enough parameters
WriteErrorValue(ACell, errArgError);
exit;
end;
// Result of function
val := func(args, fe.ParamsNum);
args.Push(val);
{
// Push valid result on stack, exit in case of error
case val.ArgumentType of
atNumber, atString, atBool, atEmpty:
@ -1478,6 +1482,7 @@ begin
exit;
end;
end;
}
end; // case
end; // for
if args.Count = 1 then begin
@ -1486,19 +1491,22 @@ begin
atNumber: WriteNumber(ACell, val.NumberValue);
atBool : WriteBoolValue(ACell, val.BoolValue);
atString: WriteUTF8Text(ACell, val.StringValue);
atError : WriteErrorValue(ACell, val.ErrorValue);
atEmpty : WriteBlank(ACell);
end;
end else
WriteErrorValue(ACell, errArgError);
{
// This case is a program error --> raise an exception
raise Exception.CreateFmt('Incorrect argument count of the formula in cell %s', [
GetCellString(ACell^.Row, ACell^.Col, [])
]);
}
finally
args.Free;
end;
end;
{@@
Converts a FPSpreadsheet cell position, which is Row, Col in numbers
and zero based, to a textual representation which is [Col][Row],

View File

@ -6,6 +6,10 @@ unit formulatests;
when the corresponding rpn formula is calculated. }
{.$DEFINE ENABLE_CALC_RPN_EXCEPTIONS}
{ Deactivate this define to include errors in the structure of the rpn formulas.
Note that Excel report a corrupted file when trying to read this file }
{.DEFINE ENABLE_DEFECTIVE_FORMULAS }
interface
@ -143,6 +147,9 @@ var
cell: PCell;
sollValues: array of TsArgument;
formula: String;
s: String;
t: TTime;
hr,min,sec,msec: Word;
begin
TempFile := GetTempFileName;

View File

@ -47,6 +47,9 @@
<UseExternalDbgSyms Value="True"/>
</Debugging>
</Linking>
<Other>
<CompilerPath Value="$(CompPath)"/>
</Other>
</CompilerOptions>
</Item2>
</BuildModes>
@ -154,6 +157,9 @@
<OptimizationLevel Value="0"/>
</Optimizations>
</CodeGeneration>
<Other>
<CompilerPath Value="$(CompPath)"/>
</Other>
</CompilerOptions>
<Debugging>
<Exceptions Count="6">

View File

@ -680,6 +680,230 @@
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(tanh(0.1));
{------------------------------------------------------------------------------}
{ Date/time functions }
{------------------------------------------------------------------------------}
// DATE
inc(Row);
MyWorksheet.WriteUTF8Text(Row, 0, '=DATE(2014, 7, 1)');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(2014,
RPNNumber(7,
RPNNumber(1,
RPNFunc(fekDATE, nil))))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(EncodeDate(2014,7,1));
// DATEVALUE
inc(Row);
s := DateToStr(EncodeDate(2014, 7, 1)); // Localization of the test
MyWorksheet.WriteUTF8Text(Row, 0, '=DATEVALUE("'+s+'")');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNString(s,
RPNFunc(fekDATEVALUE, nil))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(EncodeDate(2014,7,1));
// DAY / argument number
inc(Row);
MyWorksheet.WriteUTF8Text(Row, 0, '=DAY(DATE(2014,7,1))');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(2014,
RPNNumber(7,
RPNNumber(1,
RPNFunc(fekDATE,
RPNFunc(fekDAY, nil)))))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(1);
// DAY / argument string
inc(Row);
s := DateToStr(EncodeDate(2014, 7, 1)); // Localization of the test
MyWorksheet.WriteUTF8Text(Row, 0, '=DAY("'+s+'")');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNString(s,
RPNFunc(fekDAY, nil))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(1);
// HOUR / argument number
inc(Row);
MyWorksheet.WriteUTF8Text(Row, 0, '=HOUR(TIME(9, 59, 20))');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(9,
RPNNumber(59,
RPNNumber(20,
RPNFunc(fekTIME,
RPNFunc(fekHOUR, nil)))))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(9);
// HOUR / argument string
inc(Row);
s := TimeToStr(EncodeTime(9, 59, 20, 0)); // Localization of the test
MyWorksheet.WriteUTF8Text(Row, 0, '=HOUR("'+s+'")');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNString(s,
RPNFunc(fekHOUR, nil))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(9);
// MINUTE / argument number
inc(Row);
MyWorksheet.WriteUTF8Text(Row, 0, '=MINUTE(TIME(9, 59, 20))');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(9,
RPNNumber(59,
RPNNumber(20,
RPNFunc(fekTIME,
RPNFunc(fekMINUTE, nil)))))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(59);
// MINUTE / argument string
inc(Row);
s := TimeToStr(EncodeTime(9, 59, 20, 0)); // Localization of the test
MyWorksheet.WriteUTF8Text(Row, 0, '=MINUTE("'+s+'")');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNString(s,
RPNFunc(fekMINUTE, nil))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(59);
// MONTH / argument number
inc(Row);
MyWorksheet.WriteUTF8Text(Row, 0, '=MONTH(DATE(2014,7,1))');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(2014,
RPNNumber(7,
RPNNumber(1,
RPNFunc(fekDATE,
RPNFunc(fekMONTH, nil)))))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(7);
// MONTH / argument string
inc(Row);
s := DateToStr(EncodeDate(2014, 7, 1)); // Localization of the test
MyWorksheet.WriteUTF8Text(Row, 0, '=MONTH("'+s+'")');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNString(s,
RPNFunc(fekMONTH, nil))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(7);
// NOW
inc(Row);
// Make sure that, when the file is read we still have the same second
// Otherwise there would be a mismatch.
repeat
t := now();
DecodeTime(t, hr, min, sec, msec);
until msec < 500;
MyWorksheet.WriteUTF8Text(Row, 0, '=NOW()');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNFunc(fekNOW, nil)));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(t);
// SECOND / argument number
inc(Row);
MyWorksheet.WriteUTF8Text(Row, 0, '=SECOND(TIME(9, 59, 20))');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(9,
RPNNumber(59,
RPNNumber(20,
RPNFunc(fekTIME,
RPNFunc(fekSECOND, nil)))))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(20);
// SECOND / argument string
inc(Row);
s := TimeToStr(EncodeTime(9, 59, 20, 0)); // Localization of the test
MyWorksheet.WriteUTF8Text(Row, 0, '=SECOND("'+s+'")');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNString(s,
RPNFunc(fekSECOND, nil))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(20);
// TIME
inc(Row);
MyWorksheet.WriteUTF8Text(Row, 0, '=TIME(9, 59, 20)');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(9,
RPNNumber(59,
RPNNumber(20,
RPNFunc(fekTIME, nil))))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(EncodeTime(9, 59, 20, 0));
// TIMEVALUE
inc(Row);
s := TimeToStr(EncodeTime(9, 59, 20, 0)); // Localization!
MyWorksheet.WriteUTF8Text(Row, 0, '=TIMEVALUE("'+s+'")');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNString(s,
RPNFunc(fekTIMEVALUE, nil))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(EncodeTime(9, 59, 20, 0));
// TODAY
inc(Row);
MyWorksheet.WriteUTF8Text(Row, 0, '=TODAY()');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNFunc(fekTODAY, nil)));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(Date());
// WEEKDAY / argument number
inc(Row);
MyWorksheet.WriteUTF8Text(Row, 0, '=WEEKDAY(DATE(2014,7,1))');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(2014,
RPNNumber(7,
RPNNumber(1,
RPNFunc(fekDATE,
RPNFunc(fekWEEKDAY, 1, nil)))))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(DayOfWeek(EncodeDate(2014,7,1)));
// WEEKDAY / argument string
inc(Row);
s := DateToStr(EncodeDate(2014, 7, 1)); // Localization of the test
MyWorksheet.WriteUTF8Text(Row, 0, '=WEEKDAY("'+s+'")');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNString(s,
RPNFunc(fekWEEKDAY, 1, nil))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(DayOfWeek(EncodeDate(2014,7,1)));
// YEAR / argument number
inc(Row);
MyWorksheet.WriteUTF8Text(Row, 0, '=YEAR(DATE(2014,7,1))');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(2014,
RPNNumber(7,
RPNNumber(1,
RPNFunc(fekDATE,
RPNFunc(fekYEAR, nil)))))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(2014);
// YEAR / argument string
inc(Row);
s := DateToStr(EncodeDate(2014, 7, 1)); // Localization of the test
MyWorksheet.WriteUTF8Text(Row, 0, '=YEAR("'+s+'")');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNString(s,
RPNFunc(fekYEAR, nil))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(2014);
{------------------------------------------------------------------------------}
{ Statistical functions }
{------------------------------------------------------------------------------}
@ -1184,6 +1408,17 @@
SetLength(sollValues, Row+1);
sollValues[Row] := CreateString('d');
// SUBSTITUTE
inc(Row);
MyWorksheet.WriteUTF8Text(Row, 0, '=SUBSTITUTE("lAzArus", "A", "a")');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNString('lAzArus',
RPNString('A',
RPNString('a',
RPNFunc(fekSUBSTITUTE, 3, nil))))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateString('lazarus');
// Trim
inc(Row);
MyWorksheet.WriteUTF8Text(Row, 0, '=TRIM(" Hallo word ")');
@ -1211,3 +1446,67 @@
SetLength(sollValues, Row+1);
sollValues[Row] := CreateString(UTF8UpperCase('Viele Grüße'));
{------------------------------------------------------------------------------}
{ Information functions }
{------------------------------------------------------------------------------}
// IsError
inc(Row);
MyWorksheet.WriteUTF8Text(Row, 0, '=ISERROR(1/0)');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(1,
RPNNumber(0,
RPNFunc(fekDiv,
RPNFunc(fekISERROR, nil))))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateBool(true);
// IsError
inc(Row);
MyWorksheet.WriteUTF8Text(Row, 0, '=ISERROR(0/1)');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(0,
RPNNumber(1,
RPNFunc(fekDiv,
RPNFunc(fekISERROR, nil))))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateBool(false);
// VALUE
inc(Row);
MyWorksheet.WriteUTF8Text(Row, 0, '=VALUE("100")');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNString('100',
RPNFunc(fekVALUE, nil))));
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(100);
{------------------------------------------------------------------------------}
{ Error cases }
{------------------------------------------------------------------------------}
{$IFDEF ENABLE_DEFECTIVE_FORMULAS }
// Using less parameters than specified
inc(Row);
MyWorksheet.WriteUTF8Text(Row, 0, '=IF(true,1)');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNBool(true,
RPNNumber(1.0,
RPNFunc(fekIF,3, nil))))); // <-- we have only 2 parameters!
SetLength(sollValues, Row+1);
sollValues[Row] := CreateError(errArgError);
// Using more parameters than specified
inc(Row);
MyWorksheet.WriteUTF8Text(Row, 0, '=IF(true,1,"A")');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNBool(true,
RPNNumber(1.0,
RPNString('A',
RPNFunc(fekIF,2, nil)))))); // <-- we have 3 parameters, not 2
SetLength(sollValues, Row+1);
sollValues[Row] := CreateError(errWrongType);
{ The first idea was that this should report an ArgError, but in fact it is
a WrongType error because popping two values from the stack finds a number,
but a bool is expected }
{$ENDIF}