From 8c14c2615e2e5970149706d0077832308c9f9f14 Mon Sep 17 00:00:00 2001 From: izulin Date: Fri, 11 Sep 2020 13:50:07 +0200 Subject: [PATCH 01/22] countblank refaktor --- .../plugin/NumericAggregationPlugin.ts | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/interpreter/plugin/NumericAggregationPlugin.ts b/src/interpreter/plugin/NumericAggregationPlugin.ts index 896d52592e..917ab62e00 100644 --- a/src/interpreter/plugin/NumericAggregationPlugin.ts +++ b/src/interpreter/plugin/NumericAggregationPlugin.ts @@ -11,7 +11,7 @@ import {Maybe} from '../../Maybe' import {AstNodeType, CellRangeAst, ProcedureAst} from '../../parser' import {coerceToRange, max, maxa, min, mina} from '../ArithmeticHelper' import {SimpleRangeValue} from '../InterpreterValue' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' import {ColumnRangeAst, RowRangeAst} from '../../parser/Ast' export type BinaryOperation = (left: T, right: T) => T @@ -89,6 +89,11 @@ export class NumericAggregationPlugin extends FunctionPlugin { }, 'COUNTBLANK': { method: 'countblank', + parameters: [ + {argumentType: ArgumentTypes.SCALAR} + ], + repeatLastArgs: 1, + expandRanges: true, }, 'COUNT': { method: 'count', @@ -130,22 +135,15 @@ export class NumericAggregationPlugin extends FunctionPlugin { } public countblank(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length < 1) { - return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM, ErrorMessage.EmptyArg ) - } - let counter = 0 - for (const arg of ast.args) { - const rangeValue = coerceToRange(this.evaluateAst(arg, formulaAddress)) - for (const value of rangeValue.valuesFromTopLeftCorner()) { - if (value === EmptyValue) { + return this.runFunction(ast.args, formulaAddress, this.metadata('COUNTBLANK'), (...args) => { + let counter = 0 + for(const arg of args) { + if(arg === EmptyValue) { counter++ } } - } - return counter + return counter + }) } /** From 60ebd590df58d291d3b75c45cc8856b0502dc8ec Mon Sep 17 00:00:00 2001 From: izulin Date: Wed, 16 Sep 2020 15:04:47 +0200 Subject: [PATCH 02/22] faulty test for aggregation --- .../plugin/NumericAggregationPlugin.ts | 25 ++----- .../interpreter/aggregation-arguments.spec.ts | 75 +++++++++++++++++++ 2 files changed, 80 insertions(+), 20 deletions(-) create mode 100644 test/interpreter/aggregation-arguments.spec.ts diff --git a/src/interpreter/plugin/NumericAggregationPlugin.ts b/src/interpreter/plugin/NumericAggregationPlugin.ts index 917ab62e00..c7c84edb4e 100644 --- a/src/interpreter/plugin/NumericAggregationPlugin.ts +++ b/src/interpreter/plugin/NumericAggregationPlugin.ts @@ -135,13 +135,13 @@ export class NumericAggregationPlugin extends FunctionPlugin { } public countblank(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.metadata('COUNTBLANK'), (...args) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('COUNTBLANK'), (...args: InternalScalarValue[]) => { let counter = 0 - for(const arg of args) { + args.forEach((arg) => { if(arg === EmptyValue) { counter++ } - } + }) return counter }) } @@ -332,7 +332,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { } else { value = this.evaluateAst(arg, formulaAddress) if (value instanceof SimpleRangeValue) { - value = this.reduceRange(Array.from(value.valuesFromTopLeftCorner()).map(mapFunction), initialAccValue, reducingFunction) + value = Array.from(value.valuesFromTopLeftCorner()).map(mapFunction).reduce(reducingFunction,initialAccValue) } else if (arg.type === AstNodeType.CELL_REFERENCE) { value = mapFunction(value) value = reducingFunction(initialAccValue, value) @@ -347,21 +347,6 @@ export class NumericAggregationPlugin extends FunctionPlugin { }, initialAccValue) } - /** - * Reduces list of cell values with given reducing function - * - * @param rangeValues - list of values to reduce - * @param initialAccValue - initial accumulator value for reducing function - * @param reducingFunction - reducing function - */ - private reduceRange(rangeValues: T[], initialAccValue: T, reducingFunction: BinaryOperation): T { - let acc = initialAccValue - for (const val of rangeValues) { - acc = reducingFunction(acc, val) - } - return acc - } - /** * Performs range operation on given range * @@ -391,7 +376,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { let value = rangeVertex.getFunctionValue(functionName) as T if (!value) { const rangeValues = this.getRangeValues(functionName, range, mapFunction) - value = this.reduceRange(rangeValues, initialAccValue, reducingFunction) + value = rangeValues.reduce(reducingFunction, initialAccValue) rangeVertex.setFunctionValue(functionName, value) } diff --git a/test/interpreter/aggregation-arguments.spec.ts b/test/interpreter/aggregation-arguments.spec.ts new file mode 100644 index 0000000000..b140a75ef1 --- /dev/null +++ b/test/interpreter/aggregation-arguments.spec.ts @@ -0,0 +1,75 @@ +import {HyperFormula} from '../../src' +import {adr} from '../testUtils' + +describe('AVERAGE function', () => { + + it('should work for empty arg', () => { + const engine = HyperFormula.buildFromArray([ + ['=AVERAGE(1,)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(0.5) + }) + + it('should work for empty reference', () => { + const engine = HyperFormula.buildFromArray([ + ['=AVERAGE(A2,B2)'], + [1,null] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(1) + }) + + it('should work for range with empty val', () => { + const engine = HyperFormula.buildFromArray([ + ['=AVERAGE(A2:B2)'], + [1,null] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(1) + }) + + it('should work for empty reference + empty arg', () => { + const engine = HyperFormula.buildFromArray([ + ['=AVERAGE(A2,B2,)'], + [1,null] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(0.5) + }) + + it('should work for range with empty val + empty arg', () => { + const engine = HyperFormula.buildFromArray([ + ['=AVERAGE(A2:B2,)'], + [1,null] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(0.5) + }) + + it('should work for coercible arg', () => { + const engine = HyperFormula.buildFromArray([ + ['=AVERAGE(2,TRUE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(1.5) + }) + + it('should work for coercible value in reference', () => { + const engine = HyperFormula.buildFromArray([ + ['=AVERAGE(A2,B2)'], + [2,true] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(2) + }) + + it('should work for coercible value in range', () => { + const engine = HyperFormula.buildFromArray([ + ['=AVERAGE(A2:B2)'], + [2,true] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(2) + }) +}) From 94e79063cd51f1a3576fe79618ee41fa50817cf3 Mon Sep 17 00:00:00 2001 From: izulin Date: Wed, 16 Sep 2020 21:05:32 +0200 Subject: [PATCH 03/22] no coerce --- .../plugin/NumericAggregationPlugin.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/interpreter/plugin/NumericAggregationPlugin.ts b/src/interpreter/plugin/NumericAggregationPlugin.ts index c7c84edb4e..fd0063cb13 100644 --- a/src/interpreter/plugin/NumericAggregationPlugin.ts +++ b/src/interpreter/plugin/NumericAggregationPlugin.ts @@ -121,7 +121,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { return new CellError(ErrorType.NUM, ErrorMessage.EmptyArg ) } - return this.reduce(ast, formulaAddress, 0, 'SUM', this.interpreter.arithmeticHelper.nonstrictadd, idMap, (arg) => this.coerceScalarToNumberOrError(arg)) + return this.reduce(ast, formulaAddress, 0, 'SUM', this.interpreter.arithmeticHelper.nonstrictadd, idMap) } public sumsq(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { @@ -131,7 +131,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { return new CellError(ErrorType.NUM, ErrorMessage.EmptyArg ) } - return this.reduce(ast, formulaAddress, 0, 'SUMSQ', this.interpreter.arithmeticHelper.nonstrictadd, square, (arg) => this.coerceScalarToNumberOrError(arg)) + return this.reduce(ast, formulaAddress, 0, 'SUMSQ', this.interpreter.arithmeticHelper.nonstrictadd, square) } public countblank(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { @@ -161,7 +161,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { return new CellError(ErrorType.NUM, ErrorMessage.EmptyArg ) } - const value = this.reduce(ast, formulaAddress, Number.NEGATIVE_INFINITY, 'MAX', max, idMap, (arg) => this.coerceScalarToNumberOrError(arg)) + const value = this.reduce(ast, formulaAddress, Number.NEGATIVE_INFINITY, 'MAX', max, idMap) return zeroForInfinite(value) } @@ -173,7 +173,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { return new CellError(ErrorType.NUM, ErrorMessage.EmptyArg ) } - const value = this.reduce(ast, formulaAddress, Number.NEGATIVE_INFINITY, 'MAXA', maxa, idMap, (arg) => this.coerceScalarToNumberOrError(arg)) + const value = this.reduce(ast, formulaAddress, Number.NEGATIVE_INFINITY, 'MAXA', maxa, idMap) return zeroForInfinite(value) } @@ -193,7 +193,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { return new CellError(ErrorType.NUM, ErrorMessage.EmptyArg ) } - const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MIN', min, idMap, (arg) => this.coerceScalarToNumberOrError(arg)) + const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MIN', min, idMap) return zeroForInfinite(value) } @@ -205,7 +205,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { return new CellError(ErrorType.NUM, ErrorMessage.EmptyArg ) } - const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MINA', mina, idMap, (arg) => this.coerceScalarToNumberOrError(arg)) + const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MINA', mina, idMap) return zeroForInfinite(value) } @@ -221,7 +221,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { return left + right }, (arg): number => { return (typeof arg === 'number') ? 1 : 0 - }, (arg) => this.coerceScalarToNumberOrError(arg)) + }) return value } @@ -237,7 +237,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { return left + right }, (arg): number => { return (arg === EmptyValue) ? 0 : 1 - }, (arg) => this.coerceScalarToNumberOrError(arg)) + }) return value } @@ -266,7 +266,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { } else { return AverageResult.empty } - }, (arg) => this.coerceScalarToNumberOrError(arg)) + }) if (result instanceof CellError) { return result @@ -304,7 +304,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { return AverageResult.single(coercedArg) } } - }, (arg) => this.coerceScalarToNumberOrError(arg)) + }) if (result instanceof CellError) { return result @@ -324,7 +324,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { * @param mapFunction * @param coerceFunction * */ - private reduce(ast: ProcedureAst, formulaAddress: SimpleCellAddress, initialAccValue: T, functionName: string, reducingFunction: BinaryOperation, mapFunction: MapOperation, coerceFunction: (arg: InternalScalarValue) => InternalScalarValue): T { + private reduce(ast: ProcedureAst, formulaAddress: SimpleCellAddress, initialAccValue: T, functionName: string, reducingFunction: BinaryOperation, mapFunction: MapOperation): T { return ast.args.reduce((acc: T, arg) => { let value if (arg.type === AstNodeType.CELL_RANGE || arg.type === AstNodeType.COLUMN_RANGE || arg.type === AstNodeType.ROW_RANGE) { @@ -337,7 +337,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { value = mapFunction(value) value = reducingFunction(initialAccValue, value) } else { - value = coerceFunction(value) + value = this.coerceScalarToNumberOrError(value) value = mapFunction(value) } From 0740e985b23c5ed29cb5a071a882b3634dc0837f Mon Sep 17 00:00:00 2001 From: izulin Date: Wed, 16 Sep 2020 21:51:49 +0200 Subject: [PATCH 04/22] simpleRangeValue with filter --- src/interpreter/plugin/NumericAggregationPlugin.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/interpreter/plugin/NumericAggregationPlugin.ts b/src/interpreter/plugin/NumericAggregationPlugin.ts index fd0063cb13..49d66ab6ba 100644 --- a/src/interpreter/plugin/NumericAggregationPlugin.ts +++ b/src/interpreter/plugin/NumericAggregationPlugin.ts @@ -332,8 +332,14 @@ export class NumericAggregationPlugin extends FunctionPlugin { } else { value = this.evaluateAst(arg, formulaAddress) if (value instanceof SimpleRangeValue) { - value = Array.from(value.valuesFromTopLeftCorner()).map(mapFunction).reduce(reducingFunction,initialAccValue) + value = Array.from(value.valuesFromTopLeftCorner()) + .filter((arg) => (typeof arg === 'number')) + .map(mapFunction) + .reduce(reducingFunction,initialAccValue) } else if (arg.type === AstNodeType.CELL_REFERENCE) { + if(typeof value !== 'number') { + return acc + } value = mapFunction(value) value = reducingFunction(initialAccValue, value) } else { From c5e4b09c7e8df904e3bcfccf29c35d43ab7ab2a2 Mon Sep 17 00:00:00 2001 From: izulin Date: Wed, 16 Sep 2020 22:21:17 +0200 Subject: [PATCH 05/22] reduce better --- .../plugin/NumericAggregationPlugin.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/interpreter/plugin/NumericAggregationPlugin.ts b/src/interpreter/plugin/NumericAggregationPlugin.ts index 49d66ab6ba..9c83f7207a 100644 --- a/src/interpreter/plugin/NumericAggregationPlugin.ts +++ b/src/interpreter/plugin/NumericAggregationPlugin.ts @@ -16,13 +16,13 @@ import {ColumnRangeAst, RowRangeAst} from '../../parser/Ast' export type BinaryOperation = (left: T, right: T) => T -export type MapOperation = (arg: InternalScalarValue) => T +export type MapOperation = (arg: number) => T -function idMap(arg: InternalScalarValue): InternalScalarValue { +function idMap(arg: number): number { return arg } -function square(arg: InternalScalarValue): InternalScalarValue { +function square(arg: number): number { if (arg instanceof CellError) { return arg } else if (typeof arg === 'number') { @@ -325,15 +325,18 @@ export class NumericAggregationPlugin extends FunctionPlugin { * @param coerceFunction * */ private reduce(ast: ProcedureAst, formulaAddress: SimpleCellAddress, initialAccValue: T, functionName: string, reducingFunction: BinaryOperation, mapFunction: MapOperation): T { - return ast.args.reduce((acc: T, arg) => { + return ast.args.reduce((acc: T | CellError, arg) => { + if(acc instanceof CellError) { + return acc + } let value if (arg.type === AstNodeType.CELL_RANGE || arg.type === AstNodeType.COLUMN_RANGE || arg.type === AstNodeType.ROW_RANGE) { value = this.evaluateRange(arg, formulaAddress, acc, functionName, reducingFunction, mapFunction) } else { value = this.evaluateAst(arg, formulaAddress) if (value instanceof SimpleRangeValue) { - value = Array.from(value.valuesFromTopLeftCorner()) - .filter((arg) => (typeof arg === 'number')) + value = (Array.from(value.valuesFromTopLeftCorner()) + .filter((arg) => (typeof arg === 'number')) as number[]) .map(mapFunction) .reduce(reducingFunction,initialAccValue) } else if (arg.type === AstNodeType.CELL_REFERENCE) { @@ -344,6 +347,9 @@ export class NumericAggregationPlugin extends FunctionPlugin { value = reducingFunction(initialAccValue, value) } else { value = this.coerceScalarToNumberOrError(value) + if(value instanceof CellError) { + return value + } value = mapFunction(value) } From c3f17ec24a4d7c38e4ef0deee836fb72fa847720 Mon Sep 17 00:00:00 2001 From: izulin Date: Wed, 16 Sep 2020 22:21:41 +0200 Subject: [PATCH 06/22] typing --- src/interpreter/plugin/NumericAggregationPlugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interpreter/plugin/NumericAggregationPlugin.ts b/src/interpreter/plugin/NumericAggregationPlugin.ts index 9c83f7207a..b27560253c 100644 --- a/src/interpreter/plugin/NumericAggregationPlugin.ts +++ b/src/interpreter/plugin/NumericAggregationPlugin.ts @@ -324,7 +324,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { * @param mapFunction * @param coerceFunction * */ - private reduce(ast: ProcedureAst, formulaAddress: SimpleCellAddress, initialAccValue: T, functionName: string, reducingFunction: BinaryOperation, mapFunction: MapOperation): T { + private reduce(ast: ProcedureAst, formulaAddress: SimpleCellAddress, initialAccValue: T, functionName: string, reducingFunction: BinaryOperation, mapFunction: MapOperation): CellError | T { return ast.args.reduce((acc: T | CellError, arg) => { if(acc instanceof CellError) { return acc From fb8fd68c51ed3b77f943101d663a31523f60ec96 Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 17 Sep 2020 01:29:42 +0200 Subject: [PATCH 07/22] compiles! --- .../plugin/NumericAggregationPlugin.ts | 188 ++++++++---------- 1 file changed, 79 insertions(+), 109 deletions(-) diff --git a/src/interpreter/plugin/NumericAggregationPlugin.ts b/src/interpreter/plugin/NumericAggregationPlugin.ts index b27560253c..3976d4a1e8 100644 --- a/src/interpreter/plugin/NumericAggregationPlugin.ts +++ b/src/interpreter/plugin/NumericAggregationPlugin.ts @@ -4,6 +4,7 @@ */ import assert from 'assert' +import {type} from 'os' import {AbsoluteCellRange, DIFFERENT_SHEETS_ERROR} from '../../AbsoluteCellRange' import {CellError, EmptyValue, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ErrorMessage} from '../../error-message' @@ -18,20 +19,12 @@ export type BinaryOperation = (left: T, right: T) => T export type MapOperation = (arg: number) => T +type coercionOperation = (arg: InternalScalarValue) => Maybe + function idMap(arg: number): number { return arg } -function square(arg: number): number { - if (arg instanceof CellError) { - return arg - } else if (typeof arg === 'number') { - return arg * arg - } else { - return 0 - } -} - function zeroForInfinite(value: InternalScalarValue) { if (typeof value === 'number' && !Number.isFinite(value)) { return 0 @@ -118,20 +111,14 @@ export class NumericAggregationPlugin extends FunctionPlugin { * @param formulaAddress */ public sum(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM, ErrorMessage.EmptyArg ) - } - return this.reduce(ast, formulaAddress, 0, 'SUM', this.interpreter.arithmeticHelper.nonstrictadd, idMap) + return this.reduce(ast, formulaAddress, 0, 'SUM', (left,right) => left+right, idMap, this.strictlyNumbers) } public sumsq(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { if (ast.args.length < 1) { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM, ErrorMessage.EmptyArg ) - } - return this.reduce(ast, formulaAddress, 0, 'SUMSQ', this.interpreter.arithmeticHelper.nonstrictadd, square) + return this.reduce(ast, formulaAddress, 0, 'SUMSQ', (left, right) => left+right, (arg) => arg*arg, this.strictlyNumbers) } public countblank(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { @@ -158,10 +145,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.length < 1) { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM, ErrorMessage.EmptyArg ) - } - const value = this.reduce(ast, formulaAddress, Number.NEGATIVE_INFINITY, 'MAX', max, idMap) + const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MAX', Math.max, idMap, this.strictlyNumbers) return zeroForInfinite(value) } @@ -170,10 +154,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.length < 1) { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM, ErrorMessage.EmptyArg ) - } - const value = this.reduce(ast, formulaAddress, Number.NEGATIVE_INFINITY, 'MAXA', maxa, idMap) + const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MAXA', Math.max, idMap, this.numbersStringsBooleans) return zeroForInfinite(value) } @@ -190,10 +171,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.length < 1) { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM, ErrorMessage.EmptyArg ) - } - const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MIN', min, idMap) + const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MIN', Math.min, idMap, this.strictlyNumbers) return zeroForInfinite(value) } @@ -202,10 +180,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.length < 1) { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM, ErrorMessage.EmptyArg ) - } - const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MINA', mina, idMap) + const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MINA', Math.min, idMap, this.numbersStringsBooleans) return zeroForInfinite(value) } @@ -214,14 +189,10 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.length < 1) { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM, ErrorMessage.EmptyArg ) - } - const value = this.reduce(ast, formulaAddress, 0, 'COUNT', (left, right) => { - return left + right - }, (arg): number => { - return (typeof arg === 'number') ? 1 : 0 - }) + const value = this.reduce(ast, formulaAddress, 0, 'COUNT', + (left, right) => left + right, idMap, + (arg) => (typeof arg === 'number') ? 1 : 0 + ) return value } @@ -230,14 +201,10 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.length < 1) { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM, ErrorMessage.EmptyArg ) - } - const value = this.reduce(ast, formulaAddress, 0, 'COUNTA', (left, right) => { - return left + right - }, (arg): number => { - return (arg === EmptyValue) ? 0 : 1 - }) + const value = this.reduce(ast, formulaAddress, 0, 'COUNTA', (left, right) => left + right, + idMap, + (arg) => (arg === EmptyValue) ? 0 : 1 + ) return value } @@ -246,32 +213,18 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.length < 1) { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM, ErrorMessage.EmptyArg ) - } - - const result = this.reduce(ast, formulaAddress, AverageResult.empty, 'AVERAGE', (left, right) => { - if (left instanceof CellError) { - return left - } else if (right instanceof CellError) { - return right - } else { + const result = this.reduce(ast, formulaAddress, AverageResult.empty, 'AVERAGE', (left, right) => { return left.compose(right) - } - }, (arg): AverageResult | CellError => { - if (arg instanceof CellError) { - return arg - } else if (typeof arg === 'number') { + }, (arg): AverageResult => { return AverageResult.single(arg) - } else { - return AverageResult.empty - } - }) + }, + this.strictlyNumbers + ) if (result instanceof CellError) { return result } else { - return result.averageValue() || new CellError(ErrorType.DIV_BY_ZERO) + return result.averageValue() ?? new CellError(ErrorType.DIV_BY_ZERO) } } @@ -279,32 +232,11 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.length < 1) { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM, ErrorMessage.EmptyArg ) - } - - const result = this.reduce(ast, formulaAddress, AverageResult.empty, 'AVERAGE', (left, right) => { - if (left instanceof CellError) { - return left - } else if (right instanceof CellError) { - return right - } else { - return left.compose(right) - } - }, (arg): AverageResult | CellError => { - if (arg === EmptyValue) { - return AverageResult.empty - } else if (arg instanceof CellError) { - return arg - } else { - const coercedArg = this.interpreter.arithmeticHelper.coerceNonDateScalarToMaybeNumber(arg) - if (coercedArg === undefined) { - return AverageResult.empty - } else { - return AverageResult.single(coercedArg) - } - } - }) + const result = this.reduce(ast, formulaAddress, AverageResult.empty, 'AVERAGE', + (left, right) => left.compose(right), + (arg): AverageResult => AverageResult.single(arg), + this.numbersStringsBooleans + ) if (result instanceof CellError) { return result @@ -313,6 +245,26 @@ export class NumericAggregationPlugin extends FunctionPlugin { } } + private strictlyNumbers = (arg: InternalScalarValue): Maybe => { + if(typeof arg === 'number') { + return arg + } else { + return undefined + } + } + + private numbersStringsBooleans = (arg: InternalScalarValue): Maybe => { + let val: Maybe = arg + if(typeof arg === 'string' || typeof arg === 'boolean') { + val = this.interpreter.arithmeticHelper.coerceToMaybeNumber(val) + } + if(typeof val === 'number') { + return val + } else { + return undefined + } + } + /** * Reduces procedure arguments with given reducing function * @@ -324,27 +276,31 @@ export class NumericAggregationPlugin extends FunctionPlugin { * @param mapFunction * @param coerceFunction * */ - private reduce(ast: ProcedureAst, formulaAddress: SimpleCellAddress, initialAccValue: T, functionName: string, reducingFunction: BinaryOperation, mapFunction: MapOperation): CellError | T { + private reduce(ast: ProcedureAst, formulaAddress: SimpleCellAddress, initialAccValue: T, functionName: string, reducingFunction: BinaryOperation, mapFunction: MapOperation, coercionFunction: coercionOperation): CellError | T { return ast.args.reduce((acc: T | CellError, arg) => { if(acc instanceof CellError) { return acc } let value if (arg.type === AstNodeType.CELL_RANGE || arg.type === AstNodeType.COLUMN_RANGE || arg.type === AstNodeType.ROW_RANGE) { - value = this.evaluateRange(arg, formulaAddress, acc, functionName, reducingFunction, mapFunction) + value = this.evaluateRange(arg, formulaAddress, acc, functionName, reducingFunction, mapFunction, coercionFunction) + if(value instanceof CellError) { + return value + } } else { value = this.evaluateAst(arg, formulaAddress) if (value instanceof SimpleRangeValue) { value = (Array.from(value.valuesFromTopLeftCorner()) - .filter((arg) => (typeof arg === 'number')) as number[]) + .map(coercionFunction) + .filter((arg) => (arg !== undefined)) as number[]) .map(mapFunction) .reduce(reducingFunction,initialAccValue) } else if (arg.type === AstNodeType.CELL_REFERENCE) { - if(typeof value !== 'number') { + value = coercionFunction(value) + if (value === undefined) { return acc } value = mapFunction(value) - value = reducingFunction(initialAccValue, value) } else { value = this.coerceScalarToNumberOrError(value) if(value instanceof CellError) { @@ -368,13 +324,13 @@ export class NumericAggregationPlugin extends FunctionPlugin { * @param functionName - function name to use as cache key * @param reducingFunction - reducing function */ - private evaluateRange(ast: CellRangeAst | ColumnRangeAst | RowRangeAst, formulaAddress: SimpleCellAddress, initialAccValue: T, functionName: string, reducingFunction: BinaryOperation, mapFunction: MapOperation): T { + private evaluateRange(ast: CellRangeAst | ColumnRangeAst | RowRangeAst, formulaAddress: SimpleCellAddress, initialAccValue: T, functionName: string, reducingFunction: BinaryOperation, mapFunction: MapOperation, coercionFunction: coercionOperation): T | CellError { let range try { range = AbsoluteCellRange.fromAst(ast, formulaAddress) } catch (err) { if (err.message === DIFFERENT_SHEETS_ERROR) { - return mapFunction(new CellError(ErrorType.REF, ErrorMessage.RangeManySheets)) + return new CellError(ErrorType.REF, ErrorMessage.RangeManySheets) } else { throw err } @@ -385,10 +341,18 @@ export class NumericAggregationPlugin extends FunctionPlugin { const rangeVertex = this.dependencyGraph.getRange(rangeStart, rangeEnd)! assert.ok(rangeVertex, 'Range does not exists in graph') - let value = rangeVertex.getFunctionValue(functionName) as T + let value = rangeVertex.getFunctionValue(functionName) as (T | CellError) if (!value) { - const rangeValues = this.getRangeValues(functionName, range, mapFunction) - value = rangeValues.reduce(reducingFunction, initialAccValue) + const rangeValues = this.getRangeValues(functionName, range, mapFunction, coercionFunction) + value = rangeValues.reduce( (arg1, arg2) => { + if(arg1 instanceof CellError) { + return arg1 + } else if(arg2 instanceof CellError) { + return arg2 + } else { + return reducingFunction(arg1, arg2) + } + }, initialAccValue) rangeVertex.setFunctionValue(functionName, value) } @@ -404,7 +368,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { * @param functionName - function name (e.g. SUM) * @param range - cell range */ - private getRangeValues(functionName: string, range: AbsoluteCellRange, mapFunction: MapOperation): T[] { + private getRangeValues(functionName: string, range: AbsoluteCellRange, mapFunction: MapOperation, coercionFunction: coercionOperation): (T | CellError)[] { const rangeResult: T[] = [] const {smallerRangeVertex, restRange} = this.dependencyGraph.rangeMapping.findSmallerRange(range) const currentRangeVertex = this.dependencyGraph.getRange(range.start, range.end)! @@ -415,7 +379,10 @@ export class NumericAggregationPlugin extends FunctionPlugin { rangeResult.push(cachedValue) } else { for (const cellFromRange of smallerRangeVertex.range.addresses(this.dependencyGraph)) { - rangeResult.push(mapFunction(this.dependencyGraph.getScalarValue(cellFromRange))) + const val = coercionFunction(this.dependencyGraph.getScalarValue(cellFromRange)) + if(val !== undefined) { + rangeResult.push(mapFunction(val)) + } } } actualRange = restRange @@ -423,7 +390,10 @@ export class NumericAggregationPlugin extends FunctionPlugin { actualRange = range } for (const cellFromRange of actualRange.addresses(this.dependencyGraph)) { - rangeResult.push(mapFunction(this.dependencyGraph.getScalarValue(cellFromRange))) + const val = coercionFunction(this.dependencyGraph.getScalarValue(cellFromRange)) + if(val !== undefined) { + rangeResult.push(mapFunction(val)) + } } return rangeResult From bc86c965f28b05d71ae6c0c4c284d2952f9ce03e Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 17 Sep 2020 01:55:42 +0200 Subject: [PATCH 08/22] logic implemented --- src/interpreter/ArithmeticHelper.ts | 52 ------------------- .../plugin/NumericAggregationPlugin.ts | 21 ++++---- 2 files changed, 9 insertions(+), 64 deletions(-) diff --git a/src/interpreter/ArithmeticHelper.ts b/src/interpreter/ArithmeticHelper.ts index d2d41df385..cf30ed4004 100644 --- a/src/interpreter/ArithmeticHelper.ts +++ b/src/interpreter/ArithmeticHelper.ts @@ -341,32 +341,6 @@ export function max(left: InternalScalarValue, right: InternalScalarValue): Inte } } -export function maxa(left: InternalScalarValue, right: InternalScalarValue): InternalScalarValue { - if (left instanceof CellError) { - return left - } - if (right instanceof CellError) { - return right - } - if (typeof left === 'boolean') { - left = coerceBooleanToNumber(left) - } - if (typeof right === 'boolean') { - right = coerceBooleanToNumber(right) - } - if (typeof left === 'number') { - if (typeof right === 'number') { - return Math.max(left, right) - } else { - return left - } - } else if (typeof right === 'number') { - return right - } else { - return Number.NEGATIVE_INFINITY - } -} - /** * Returns min from two numbers * @@ -397,32 +371,6 @@ export function min(left: InternalScalarValue, right: InternalScalarValue): Inte } } -export function mina(left: InternalScalarValue, right: InternalScalarValue): InternalScalarValue { - if (left instanceof CellError) { - return left - } - if (right instanceof CellError) { - return right - } - if (typeof left === 'boolean') { - left = coerceBooleanToNumber(left) - } - if (typeof right === 'boolean') { - right = coerceBooleanToNumber(right) - } - if (typeof left === 'number') { - if (typeof right === 'number') { - return Math.min(left, right) - } else { - return left - } - } else if (typeof right === 'number') { - return right - } else { - return Number.POSITIVE_INFINITY - } -} - export function numberCmp(left: number, right: number): number { if (left > right) { return 1 diff --git a/src/interpreter/plugin/NumericAggregationPlugin.ts b/src/interpreter/plugin/NumericAggregationPlugin.ts index 3976d4a1e8..6fdadce5e5 100644 --- a/src/interpreter/plugin/NumericAggregationPlugin.ts +++ b/src/interpreter/plugin/NumericAggregationPlugin.ts @@ -4,13 +4,12 @@ */ import assert from 'assert' -import {type} from 'os' import {AbsoluteCellRange, DIFFERENT_SHEETS_ERROR} from '../../AbsoluteCellRange' import {CellError, EmptyValue, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ErrorMessage} from '../../error-message' import {Maybe} from '../../Maybe' import {AstNodeType, CellRangeAst, ProcedureAst} from '../../parser' -import {coerceToRange, max, maxa, min, mina} from '../ArithmeticHelper' +import {coerceBooleanToNumber} from '../ArithmeticHelper' import {SimpleRangeValue} from '../InterpreterValue' import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' import {ColumnRangeAst, RowRangeAst} from '../../parser/Ast' @@ -154,7 +153,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.length < 1) { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } - const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MAXA', Math.max, idMap, this.numbersStringsBooleans) + const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MAXA', Math.max, idMap, this.numbersBooleans) return zeroForInfinite(value) } @@ -180,7 +179,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.length < 1) { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } - const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MINA', Math.min, idMap, this.numbersStringsBooleans) + const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MINA', Math.min, idMap, this.numbersBooleans) return zeroForInfinite(value) } @@ -235,7 +234,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { const result = this.reduce(ast, formulaAddress, AverageResult.empty, 'AVERAGE', (left, right) => left.compose(right), (arg): AverageResult => AverageResult.single(arg), - this.numbersStringsBooleans + this.numbersBooleans ) if (result instanceof CellError) { @@ -253,13 +252,11 @@ export class NumericAggregationPlugin extends FunctionPlugin { } } - private numbersStringsBooleans = (arg: InternalScalarValue): Maybe => { - let val: Maybe = arg - if(typeof arg === 'string' || typeof arg === 'boolean') { - val = this.interpreter.arithmeticHelper.coerceToMaybeNumber(val) - } - if(typeof val === 'number') { - return val + private numbersBooleans = (arg: InternalScalarValue): Maybe => { + if(typeof arg === 'boolean') { + return coerceBooleanToNumber(arg) + } else if(typeof arg === 'number') { + return arg } else { return undefined } From 37b46edd198b8c54570f7059878bdd12dfac27eb Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 17 Sep 2020 01:59:14 +0200 Subject: [PATCH 09/22] min,max redundant --- src/interpreter/ArithmeticHelper.ts | 60 ----------------------------- test/interpreter/scalar.spec.ts | 47 +--------------------- 2 files changed, 1 insertion(+), 106 deletions(-) diff --git a/src/interpreter/ArithmeticHelper.ts b/src/interpreter/ArithmeticHelper.ts index cf30ed4004..764f827195 100644 --- a/src/interpreter/ArithmeticHelper.ts +++ b/src/interpreter/ArithmeticHelper.ts @@ -311,66 +311,6 @@ export function divide(left: number, right: number): number | CellError { } } -/** - * Returns max from two numbers - * - * Implementation of max function which is used in interpreter. - * - * Errors are propagated, non-numerical values are neutral. - * - * @param left - left operand of addition - * @param right - right operand of addition - */ -export function max(left: InternalScalarValue, right: InternalScalarValue): InternalScalarValue { - if (left instanceof CellError) { - return left - } - if (right instanceof CellError) { - return right - } - if (typeof left === 'number') { - if (typeof right === 'number') { - return Math.max(left, right) - } else { - return left - } - } else if (typeof right === 'number') { - return right - } else { - return Number.NEGATIVE_INFINITY - } -} - -/** - * Returns min from two numbers - * - * Implementation of min function which is used in interpreter. - * - * Errors are propagated, non-numerical values are neutral. - * - * @param left - left operand of addition - * @param right - right operand of addition - */ -export function min(left: InternalScalarValue, right: InternalScalarValue): InternalScalarValue { - if (left instanceof CellError) { - return left - } - if (right instanceof CellError) { - return right - } - if (typeof left === 'number') { - if (typeof right === 'number') { - return Math.min(left, right) - } else { - return left - } - } else if (typeof right === 'number') { - return right - } else { - return Number.POSITIVE_INFINITY - } -} - export function numberCmp(left: number, right: number): number { if (left > right) { return 1 diff --git a/test/interpreter/scalar.spec.ts b/test/interpreter/scalar.spec.ts index 661773b9c3..494034782a 100644 --- a/test/interpreter/scalar.spec.ts +++ b/test/interpreter/scalar.spec.ts @@ -1,7 +1,7 @@ import {ErrorType} from '../../src' import {Config} from '../../src/Config' import {DateTimeHelper} from '../../src/DateTimeHelper' -import {ArithmeticHelper, max, min} from '../../src/interpreter/ArithmeticHelper' +import {ArithmeticHelper} from '../../src/interpreter/ArithmeticHelper' import {NumberLiteralHelper} from '../../src/NumberLiteralHelper' import {CellError} from '../../src/Cell' @@ -33,48 +33,3 @@ describe('nonstrictadd', () => { }) }) -describe('max', () => { - it('max', () => { - expect(max(2, 3)).toEqual(3) - }) - - it('return error of right operand', () => { - expect(max(2, new CellError(ErrorType.DIV_BY_ZERO))).toEqual(new CellError(ErrorType.DIV_BY_ZERO)) - }) - - it('return error of left operand if both present', () => { - expect(max(new CellError(ErrorType.NA), new CellError(ErrorType.DIV_BY_ZERO))).toEqual(new CellError(ErrorType.NA)) - }) - - it('ignores non-numerics', () => { - expect(max('foo', 5)).toEqual(5) - expect(max(5, 'foo')).toEqual(5) - }) - - it('returns negative infinity if only non-numerics', () => { - expect(max('bar', 'foo')).toEqual(Number.NEGATIVE_INFINITY) - }) -}) - -describe('min', () => { - it('min', () => { - expect(min(2, 3)).toEqual(2) - }) - - it('return error of right operand', () => { - expect(min(2, new CellError(ErrorType.DIV_BY_ZERO))).toEqual(new CellError(ErrorType.DIV_BY_ZERO)) - }) - - it('return error of left operand if both present', () => { - expect(min(new CellError(ErrorType.NA), new CellError(ErrorType.DIV_BY_ZERO))).toEqual(new CellError(ErrorType.NA)) - }) - - it('ignores non-numerics', () => { - expect(min('foo', 5)).toEqual(5) - expect(min(-5, 'foo')).toEqual(-5) - }) - - it('returns positive infinity if only non-numerics', () => { - expect(min('bar', 'foo')).toEqual(Number.POSITIVE_INFINITY) - }) -}) From b61332e634764031031459c07637a9994c959d8b Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 17 Sep 2020 11:17:22 +0200 Subject: [PATCH 10/22] errors correctly handled --- .../plugin/NumericAggregationPlugin.ts | 73 ++++++++++--------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/src/interpreter/plugin/NumericAggregationPlugin.ts b/src/interpreter/plugin/NumericAggregationPlugin.ts index 6fdadce5e5..7ac50a4d52 100644 --- a/src/interpreter/plugin/NumericAggregationPlugin.ts +++ b/src/interpreter/plugin/NumericAggregationPlugin.ts @@ -18,7 +18,7 @@ export type BinaryOperation = (left: T, right: T) => T export type MapOperation = (arg: number) => T -type coercionOperation = (arg: InternalScalarValue) => Maybe +type coercionOperation = (arg: InternalScalarValue) => Maybe function idMap(arg: number): number { return arg @@ -144,7 +144,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.length < 1) { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } - const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MAX', Math.max, idMap, this.strictlyNumbers) + const value = this.reduce(ast, formulaAddress, Number.NEGATIVE_INFINITY, 'MAX', Math.max, idMap, this.strictlyNumbers) return zeroForInfinite(value) } @@ -153,7 +153,9 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.length < 1) { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } - const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MAXA', Math.max, idMap, this.numbersBooleans) + const value = this.reduce(ast, formulaAddress, Number.NEGATIVE_INFINITY, 'MAXA', + (left, right) => Math.max(left,right), + idMap, this.numbersBooleans) return zeroForInfinite(value) } @@ -244,18 +246,18 @@ export class NumericAggregationPlugin extends FunctionPlugin { } } - private strictlyNumbers = (arg: InternalScalarValue): Maybe => { - if(typeof arg === 'number') { + private strictlyNumbers = (arg: InternalScalarValue): Maybe => { + if(typeof arg === 'number' || arg instanceof CellError) { return arg } else { return undefined } } - private numbersBooleans = (arg: InternalScalarValue): Maybe => { + private numbersBooleans = (arg: InternalScalarValue): Maybe => { if(typeof arg === 'boolean') { return coerceBooleanToNumber(arg) - } else if(typeof arg === 'number') { + } else if(typeof arg === 'number' || arg instanceof CellError) { return arg } else { return undefined @@ -278,37 +280,32 @@ export class NumericAggregationPlugin extends FunctionPlugin { if(acc instanceof CellError) { return acc } - let value if (arg.type === AstNodeType.CELL_RANGE || arg.type === AstNodeType.COLUMN_RANGE || arg.type === AstNodeType.ROW_RANGE) { - value = this.evaluateRange(arg, formulaAddress, acc, functionName, reducingFunction, mapFunction, coercionFunction) - if(value instanceof CellError) { - return value + return this.evaluateRange(arg, formulaAddress, acc, functionName, reducingFunction, mapFunction, coercionFunction) + } + let value + value = this.evaluateAst(arg, formulaAddress) + if (value instanceof SimpleRangeValue) { + return (Array.from(value.valuesFromTopLeftCorner()) + .map(coercionFunction) + .filter((arg) => (arg !== undefined)) as number[]) + .map(mapFunction) + .reduce(reducingFunction,acc) + } + + if (arg.type === AstNodeType.CELL_REFERENCE) { + value = coercionFunction(value) + if (value === undefined) { + return acc } } else { - value = this.evaluateAst(arg, formulaAddress) - if (value instanceof SimpleRangeValue) { - value = (Array.from(value.valuesFromTopLeftCorner()) - .map(coercionFunction) - .filter((arg) => (arg !== undefined)) as number[]) - .map(mapFunction) - .reduce(reducingFunction,initialAccValue) - } else if (arg.type === AstNodeType.CELL_REFERENCE) { - value = coercionFunction(value) - if (value === undefined) { - return acc - } - value = mapFunction(value) - } else { - value = this.coerceScalarToNumberOrError(value) - if(value instanceof CellError) { - return value - } - value = mapFunction(value) - } - + value = this.coerceScalarToNumberOrError(value) + } + if(value instanceof CellError) { + return value } - return reducingFunction(acc, value) + return reducingFunction(acc, mapFunction(value)) }, initialAccValue) } @@ -366,7 +363,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { * @param range - cell range */ private getRangeValues(functionName: string, range: AbsoluteCellRange, mapFunction: MapOperation, coercionFunction: coercionOperation): (T | CellError)[] { - const rangeResult: T[] = [] + const rangeResult: (T | CellError)[] = [] const {smallerRangeVertex, restRange} = this.dependencyGraph.rangeMapping.findSmallerRange(range) const currentRangeVertex = this.dependencyGraph.getRange(range.start, range.end)! let actualRange: AbsoluteCellRange @@ -377,7 +374,9 @@ export class NumericAggregationPlugin extends FunctionPlugin { } else { for (const cellFromRange of smallerRangeVertex.range.addresses(this.dependencyGraph)) { const val = coercionFunction(this.dependencyGraph.getScalarValue(cellFromRange)) - if(val !== undefined) { + if(val instanceof CellError) { + rangeResult.push(val) + } else if(val !== undefined) { rangeResult.push(mapFunction(val)) } } @@ -388,7 +387,9 @@ export class NumericAggregationPlugin extends FunctionPlugin { } for (const cellFromRange of actualRange.addresses(this.dependencyGraph)) { const val = coercionFunction(this.dependencyGraph.getScalarValue(cellFromRange)) - if(val !== undefined) { + if(val instanceof CellError) { + rangeResult.push(val) + } else if(val !== undefined) { rangeResult.push(mapFunction(val)) } } From 207f6480e48a329f89ce1a3fa9cdd07868bb9726 Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 17 Sep 2020 11:48:33 +0200 Subject: [PATCH 11/22] errors --- .../plugin/NumericAggregationPlugin.ts | 23 +++++++++++++++---- test/interpreter/function-averagea.spec.ts | 2 +- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/interpreter/plugin/NumericAggregationPlugin.ts b/src/interpreter/plugin/NumericAggregationPlugin.ts index 7ac50a4d52..7f7370aaf9 100644 --- a/src/interpreter/plugin/NumericAggregationPlugin.ts +++ b/src/interpreter/plugin/NumericAggregationPlugin.ts @@ -110,14 +110,17 @@ export class NumericAggregationPlugin extends FunctionPlugin { * @param formulaAddress */ public sum(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.reduce(ast, formulaAddress, 0, 'SUM', (left,right) => left+right, idMap, this.strictlyNumbers) + if (ast.args.length < 1) { + return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) + } + return this.reduce(ast, formulaAddress, 0, 'SUM', this.addWithEpsilon, idMap, this.strictlyNumbers) } public sumsq(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { if (ast.args.length < 1) { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } - return this.reduce(ast, formulaAddress, 0, 'SUMSQ', (left, right) => left+right, (arg) => arg*arg, this.strictlyNumbers) + return this.reduce(ast, formulaAddress, 0, 'SUMSQ', this.addWithEpsilon, (arg) => arg*arg, this.strictlyNumbers) } public countblank(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { @@ -144,7 +147,9 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.length < 1) { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } - const value = this.reduce(ast, formulaAddress, Number.NEGATIVE_INFINITY, 'MAX', Math.max, idMap, this.strictlyNumbers) + const value = this.reduce(ast, formulaAddress, Number.NEGATIVE_INFINITY, 'MAX', + (left, right) => Math.max(left,right), + idMap, this.strictlyNumbers) return zeroForInfinite(value) } @@ -172,7 +177,9 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.length < 1) { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } - const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MIN', Math.min, idMap, this.strictlyNumbers) + const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MIN', + (left, right) => Math.min(left,right), + idMap, this.strictlyNumbers) return zeroForInfinite(value) } @@ -181,7 +188,9 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.length < 1) { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } - const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MINA', Math.min, idMap, this.numbersBooleans) + const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MINA', + (left, right) => Math.min(left,right), + idMap, this.numbersBooleans) return zeroForInfinite(value) } @@ -259,11 +268,15 @@ export class NumericAggregationPlugin extends FunctionPlugin { return coerceBooleanToNumber(arg) } else if(typeof arg === 'number' || arg instanceof CellError) { return arg + } else if(typeof arg === 'string') { + return 0 } else { return undefined } } + private addWithEpsilon = (left: number, right: number): number => this.interpreter.arithmeticHelper.addWithEpsilon(left, right) + /** * Reduces procedure arguments with given reducing function * diff --git a/test/interpreter/function-averagea.spec.ts b/test/interpreter/function-averagea.spec.ts index a439d376bb..ee76b14fc0 100644 --- a/test/interpreter/function-averagea.spec.ts +++ b/test/interpreter/function-averagea.spec.ts @@ -36,7 +36,7 @@ describe('AVERAGEA', () => { ['39', null, '=AVERAGEA(A3:B3)'], ]) - expect(engine.getCellValue(adr('C1'))).toEqual(20) + expect(engine.getCellValue(adr('C1'))).toEqual(19.5) expect(engine.getCellValue(adr('C2'))).toEqual(20) expect(engine.getCellValue(adr('C3'))).toEqual(39) }) From 00e4ecde4a784a5d9e0d326a5e9f3b599d5f037a Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 17 Sep 2020 22:03:53 +0200 Subject: [PATCH 12/22] should work now --- .../plugin/NumericAggregationPlugin.ts | 24 ++++++++++++++++--- .../interpreter/aggregation-arguments.spec.ts | 9 +++++++ test/interpreter/function-averagea.spec.ts | 7 +++--- test/interpreter/function-count.spec.ts | 5 ++-- test/interpreter/function-counta.spec.ts | 2 +- test/interpreter/function-maxa.spec.ts | 10 +++++--- test/interpreter/function-mina.spec.ts | 8 +++++-- test/interpreter/function-sum.spec.ts | 5 ++-- test/optional-parameters.spec.ts | 2 +- 9 files changed, 53 insertions(+), 19 deletions(-) diff --git a/src/interpreter/plugin/NumericAggregationPlugin.ts b/src/interpreter/plugin/NumericAggregationPlugin.ts index 7f7370aaf9..03729b60da 100644 --- a/src/interpreter/plugin/NumericAggregationPlugin.ts +++ b/src/interpreter/plugin/NumericAggregationPlugin.ts @@ -301,9 +301,23 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (value instanceof SimpleRangeValue) { return (Array.from(value.valuesFromTopLeftCorner()) .map(coercionFunction) - .filter((arg) => (arg !== undefined)) as number[]) - .map(mapFunction) - .reduce(reducingFunction,acc) + .filter((arg) => (arg !== undefined)) as (CellError | number)[]) + .map((arg) => { + if(arg instanceof CellError){ + return arg + } else { + return mapFunction(arg) + } + }) + .reduce((left,right) => { + if(left instanceof CellError) { + return left + } else if(right instanceof CellError) { + return right + } else { + return reducingFunction(left,right) + } + },acc) } if (arg.type === AstNodeType.CELL_REFERENCE) { @@ -313,6 +327,10 @@ export class NumericAggregationPlugin extends FunctionPlugin { } } else { value = this.coerceScalarToNumberOrError(value) + value = coercionFunction(value) + if (value === undefined) { + return acc + } } if(value instanceof CellError) { return value diff --git a/test/interpreter/aggregation-arguments.spec.ts b/test/interpreter/aggregation-arguments.spec.ts index b140a75ef1..03054662e5 100644 --- a/test/interpreter/aggregation-arguments.spec.ts +++ b/test/interpreter/aggregation-arguments.spec.ts @@ -73,3 +73,12 @@ describe('AVERAGE function', () => { expect(engine.getCellValue(adr('A1'))).toEqual(2) }) }) + +describe('COUNT function', () => { + it('should work with explicit error in arg', () => { + const engine = HyperFormula.buildFromArray([ + ['=COUNT(NA())'], + ]) + expect(engine.getCellValue(adr('A1'))).toEqual(0) + }) +}) diff --git a/test/interpreter/function-averagea.spec.ts b/test/interpreter/function-averagea.spec.ts index ee76b14fc0..4dac5b511b 100644 --- a/test/interpreter/function-averagea.spec.ts +++ b/test/interpreter/function-averagea.spec.ts @@ -43,12 +43,13 @@ describe('AVERAGEA', () => { it('error when no meaningful arguments', () => { const engine = HyperFormula.buildFromArray([ - ['foo'], - [null], - ['=AVERAGEA(A1:A2)'] + [null, 'foo'], + [null, null], + ['=AVERAGEA(A1:A2)', '=AVERAGEA(B1:B2)'] ]) expect(engine.getCellValue(adr('A3'))).toEqual(detailedError(ErrorType.DIV_BY_ZERO)) + expect(engine.getCellValue(adr('B3'))).toEqual(0) }) it('over a range value', () => { diff --git a/test/interpreter/function-count.spec.ts b/test/interpreter/function-count.spec.ts index 47cbf04956..dae7567412 100644 --- a/test/interpreter/function-count.spec.ts +++ b/test/interpreter/function-count.spec.ts @@ -1,5 +1,4 @@ -import {HyperFormula} from '../../src' -import {ErrorType} from '../../src/Cell' +import {ErrorType, HyperFormula} from '../../src' import {ErrorMessage} from '../../src/error-message' import {adr, detailedError} from '../testUtils' @@ -38,7 +37,7 @@ describe('COUNT', () => { expect(engine.getCellValue(adr('A3'))).toEqual(4) }) - it('error ranges doesnt count', () => { + it('error in ranges', () => { const engine = HyperFormula.buildFromArray([ ['1', '2'], ['3', '4'], diff --git a/test/interpreter/function-counta.spec.ts b/test/interpreter/function-counta.spec.ts index 831b6ea914..040051e38f 100644 --- a/test/interpreter/function-counta.spec.ts +++ b/test/interpreter/function-counta.spec.ts @@ -38,7 +38,7 @@ describe('COUNTA', () => { expect(engine.getCellValue(adr('A3'))).toEqual(4) }) - it('error ranges doesnt count', () => { + it('error in ranges', () => { const engine = HyperFormula.buildFromArray([ ['1', '2'], ['3', '4'], diff --git a/test/interpreter/function-maxa.spec.ts b/test/interpreter/function-maxa.spec.ts index e4a24c4faa..0044e969ea 100644 --- a/test/interpreter/function-maxa.spec.ts +++ b/test/interpreter/function-maxa.spec.ts @@ -38,7 +38,7 @@ describe('MAXA', () => { it('MAXA of strings and -1', () => { const engine = HyperFormula.buildFromArray([['foo'], ['bar'], ['-1'], ['=MAXA(A1:A3)']]) - expect(engine.getCellValue(adr('A4'))).toEqual(-1) + expect(engine.getCellValue(adr('A4'))).toEqual(0) }) it('MAXA of empty value', () => { @@ -47,8 +47,12 @@ describe('MAXA', () => { }) it('MAXA of empty value and some negative number', () => { - const engine = HyperFormula.buildFromArray([['', '-1', '=MAXA(A1,B1)']]) - expect(engine.getCellValue(adr('C1'))).toEqual(-1) + const engine = HyperFormula.buildFromArray([ + ['', '-1', '=MAXA(A1,B1)'], + [null, '-1', '=MAXA(A2,B2)'], + ]) + expect(engine.getCellValue(adr('C1'))).toEqual(0) + expect(engine.getCellValue(adr('C2'))).toEqual(-1) }) it('over a range value', () => { diff --git a/test/interpreter/function-mina.spec.ts b/test/interpreter/function-mina.spec.ts index 5a52e628db..3980d2623c 100644 --- a/test/interpreter/function-mina.spec.ts +++ b/test/interpreter/function-mina.spec.ts @@ -43,9 +43,13 @@ describe('MINA', () => { }) it('MINA of empty value and some positive number', () => { - const engine = HyperFormula.buildFromArray([['', '1', '=MINA(A1,B1)']]) + const engine = HyperFormula.buildFromArray([ + ['', '1', '=MINA(A1,B1)'], + [null, '1', '=MINA(A2,B2)'], + ]) - expect(engine.getCellValue(adr('C1'))).toEqual(1) + expect(engine.getCellValue(adr('C1'))).toEqual(0) + expect(engine.getCellValue(adr('C2'))).toEqual(1) }) it('over a range value', () => { diff --git a/test/interpreter/function-sum.spec.ts b/test/interpreter/function-sum.spec.ts index 6a45d79221..159e077989 100644 --- a/test/interpreter/function-sum.spec.ts +++ b/test/interpreter/function-sum.spec.ts @@ -1,5 +1,4 @@ -import {HyperFormula} from '../../src' -import {ErrorType} from '../../src/Cell' +import {ErrorType, HyperFormula} from '../../src' import {ErrorMessage} from '../../src/error-message' import {adr, detailedError} from '../testUtils' @@ -7,7 +6,7 @@ describe('SUM', () => { it('SUM without args', () => { const engine = HyperFormula.buildFromArray([['=SUM()']]) - expect(engine.getCellValue(adr('A1'))).toEqual(0) + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) }) it('SUM with args', () => { diff --git a/test/optional-parameters.spec.ts b/test/optional-parameters.spec.ts index c5bd52320b..2c1cafda7f 100644 --- a/test/optional-parameters.spec.ts +++ b/test/optional-parameters.spec.ts @@ -61,7 +61,7 @@ describe('Nonexistent metadata', () => { ]) expect(engine.getCellValue(adr('A1'))).toEqual(1901) - expect(engine.getCellValue(adr('A2'))).toEqual(detailedError(ErrorType.NUM, ErrorMessage.EmptyArg )) //TODO when SUM() is fixed, it should evaluate to 1 + expect(engine.getCellValue(adr('A2'))).toEqual(1) expect(engine.getCellValue(adr('A3'))).toEqual('abcd') }) From 02b8c46a1738081ddea35b522c986312ac161f51 Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 17 Sep 2020 22:06:49 +0200 Subject: [PATCH 13/22] minor fixes --- src/interpreter/plugin/NumericAggregationPlugin.ts | 14 +++++++------- test/interpreter/aggregation-arguments.spec.ts | 12 ++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/interpreter/plugin/NumericAggregationPlugin.ts b/src/interpreter/plugin/NumericAggregationPlugin.ts index 03729b60da..1cb54daac6 100644 --- a/src/interpreter/plugin/NumericAggregationPlugin.ts +++ b/src/interpreter/plugin/NumericAggregationPlugin.ts @@ -148,7 +148,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } const value = this.reduce(ast, formulaAddress, Number.NEGATIVE_INFINITY, 'MAX', - (left, right) => Math.max(left,right), + (left, right) => Math.max(left, right), idMap, this.strictlyNumbers) return zeroForInfinite(value) @@ -159,7 +159,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } const value = this.reduce(ast, formulaAddress, Number.NEGATIVE_INFINITY, 'MAXA', - (left, right) => Math.max(left,right), + (left, right) => Math.max(left, right), idMap, this.numbersBooleans) return zeroForInfinite(value) @@ -178,7 +178,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MIN', - (left, right) => Math.min(left,right), + (left, right) => Math.min(left, right), idMap, this.strictlyNumbers) return zeroForInfinite(value) @@ -189,7 +189,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MINA', - (left, right) => Math.min(left,right), + (left, right) => Math.min(left, right), idMap, this.numbersBooleans) return zeroForInfinite(value) @@ -309,15 +309,15 @@ export class NumericAggregationPlugin extends FunctionPlugin { return mapFunction(arg) } }) - .reduce((left,right) => { + .reduce((left, right) => { if(left instanceof CellError) { return left } else if(right instanceof CellError) { return right } else { - return reducingFunction(left,right) + return reducingFunction(left, right) } - },acc) + }, acc) } if (arg.type === AstNodeType.CELL_REFERENCE) { diff --git a/test/interpreter/aggregation-arguments.spec.ts b/test/interpreter/aggregation-arguments.spec.ts index 03054662e5..180fa77a7f 100644 --- a/test/interpreter/aggregation-arguments.spec.ts +++ b/test/interpreter/aggregation-arguments.spec.ts @@ -14,7 +14,7 @@ describe('AVERAGE function', () => { it('should work for empty reference', () => { const engine = HyperFormula.buildFromArray([ ['=AVERAGE(A2,B2)'], - [1,null] + [1, null] ]) expect(engine.getCellValue(adr('A1'))).toEqual(1) @@ -23,7 +23,7 @@ describe('AVERAGE function', () => { it('should work for range with empty val', () => { const engine = HyperFormula.buildFromArray([ ['=AVERAGE(A2:B2)'], - [1,null] + [1, null] ]) expect(engine.getCellValue(adr('A1'))).toEqual(1) @@ -32,7 +32,7 @@ describe('AVERAGE function', () => { it('should work for empty reference + empty arg', () => { const engine = HyperFormula.buildFromArray([ ['=AVERAGE(A2,B2,)'], - [1,null] + [1, null] ]) expect(engine.getCellValue(adr('A1'))).toEqual(0.5) @@ -41,7 +41,7 @@ describe('AVERAGE function', () => { it('should work for range with empty val + empty arg', () => { const engine = HyperFormula.buildFromArray([ ['=AVERAGE(A2:B2,)'], - [1,null] + [1, null] ]) expect(engine.getCellValue(adr('A1'))).toEqual(0.5) @@ -58,7 +58,7 @@ describe('AVERAGE function', () => { it('should work for coercible value in reference', () => { const engine = HyperFormula.buildFromArray([ ['=AVERAGE(A2,B2)'], - [2,true] + [2, true] ]) expect(engine.getCellValue(adr('A1'))).toEqual(2) @@ -67,7 +67,7 @@ describe('AVERAGE function', () => { it('should work for coercible value in range', () => { const engine = HyperFormula.buildFromArray([ ['=AVERAGE(A2:B2)'], - [2,true] + [2, true] ]) expect(engine.getCellValue(adr('A1'))).toEqual(2) From a01bc9b373b5b057649041124a81ecd3d325707d Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 17 Sep 2020 22:08:13 +0200 Subject: [PATCH 14/22] linter --- .../plugin/NumericAggregationPlugin.ts | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/src/interpreter/plugin/NumericAggregationPlugin.ts b/src/interpreter/plugin/NumericAggregationPlugin.ts index 1cb54daac6..17b4ab1bee 100644 --- a/src/interpreter/plugin/NumericAggregationPlugin.ts +++ b/src/interpreter/plugin/NumericAggregationPlugin.ts @@ -113,14 +113,14 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.length < 1) { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } - return this.reduce(ast, formulaAddress, 0, 'SUM', this.addWithEpsilon, idMap, this.strictlyNumbers) + return this.reduce(ast, formulaAddress, 0, 'SUM', this.addWithEpsilon, idMap, strictlyNumbers) } public sumsq(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { if (ast.args.length < 1) { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } - return this.reduce(ast, formulaAddress, 0, 'SUMSQ', this.addWithEpsilon, (arg) => arg*arg, this.strictlyNumbers) + return this.reduce(ast, formulaAddress, 0, 'SUMSQ', this.addWithEpsilon, (arg) => arg*arg, strictlyNumbers) } public countblank(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { @@ -149,7 +149,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { } const value = this.reduce(ast, formulaAddress, Number.NEGATIVE_INFINITY, 'MAX', (left, right) => Math.max(left, right), - idMap, this.strictlyNumbers) + idMap, strictlyNumbers) return zeroForInfinite(value) } @@ -160,7 +160,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { } const value = this.reduce(ast, formulaAddress, Number.NEGATIVE_INFINITY, 'MAXA', (left, right) => Math.max(left, right), - idMap, this.numbersBooleans) + idMap, numbersBooleans) return zeroForInfinite(value) } @@ -179,7 +179,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { } const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MIN', (left, right) => Math.min(left, right), - idMap, this.strictlyNumbers) + idMap, strictlyNumbers) return zeroForInfinite(value) } @@ -190,7 +190,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { } const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MINA', (left, right) => Math.min(left, right), - idMap, this.numbersBooleans) + idMap, numbersBooleans) return zeroForInfinite(value) } @@ -228,7 +228,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { }, (arg): AverageResult => { return AverageResult.single(arg) }, - this.strictlyNumbers + strictlyNumbers ) if (result instanceof CellError) { @@ -245,7 +245,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { const result = this.reduce(ast, formulaAddress, AverageResult.empty, 'AVERAGE', (left, right) => left.compose(right), (arg): AverageResult => AverageResult.single(arg), - this.numbersBooleans + numbersBooleans ) if (result instanceof CellError) { @@ -255,26 +255,6 @@ export class NumericAggregationPlugin extends FunctionPlugin { } } - private strictlyNumbers = (arg: InternalScalarValue): Maybe => { - if(typeof arg === 'number' || arg instanceof CellError) { - return arg - } else { - return undefined - } - } - - private numbersBooleans = (arg: InternalScalarValue): Maybe => { - if(typeof arg === 'boolean') { - return coerceBooleanToNumber(arg) - } else if(typeof arg === 'number' || arg instanceof CellError) { - return arg - } else if(typeof arg === 'string') { - return 0 - } else { - return undefined - } - } - private addWithEpsilon = (left: number, right: number): number => this.interpreter.arithmeticHelper.addWithEpsilon(left, right) /** @@ -428,3 +408,24 @@ export class NumericAggregationPlugin extends FunctionPlugin { return rangeResult } } + +function strictlyNumbers(arg: InternalScalarValue): Maybe { + if(typeof arg === 'number' || arg instanceof CellError) { + return arg + } else { + return undefined + } +} + +function numbersBooleans(arg: InternalScalarValue): Maybe { + if(typeof arg === 'boolean') { + return coerceBooleanToNumber(arg) + } else if(typeof arg === 'number' || arg instanceof CellError) { + return arg + } else if(typeof arg === 'string') { + return 0 + } else { + return undefined + } +} + From 72d43fe0ed8cf0338c0b626f6207d49c2ede507a Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 17 Sep 2020 22:30:58 +0200 Subject: [PATCH 15/22] moving tests around --- test/interpreter/aggregation-arguments.spec.ts | 10 ---------- test/interpreter/function-count.spec.ts | 15 +++++++++++++++ test/interpreter/function-counta.spec.ts | 15 +++++++++++++++ 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/test/interpreter/aggregation-arguments.spec.ts b/test/interpreter/aggregation-arguments.spec.ts index 180fa77a7f..ab10cbba1d 100644 --- a/test/interpreter/aggregation-arguments.spec.ts +++ b/test/interpreter/aggregation-arguments.spec.ts @@ -2,7 +2,6 @@ import {HyperFormula} from '../../src' import {adr} from '../testUtils' describe('AVERAGE function', () => { - it('should work for empty arg', () => { const engine = HyperFormula.buildFromArray([ ['=AVERAGE(1,)'], @@ -73,12 +72,3 @@ describe('AVERAGE function', () => { expect(engine.getCellValue(adr('A1'))).toEqual(2) }) }) - -describe('COUNT function', () => { - it('should work with explicit error in arg', () => { - const engine = HyperFormula.buildFromArray([ - ['=COUNT(NA())'], - ]) - expect(engine.getCellValue(adr('A1'))).toEqual(0) - }) -}) diff --git a/test/interpreter/function-count.spec.ts b/test/interpreter/function-count.spec.ts index dae7567412..65f02870e3 100644 --- a/test/interpreter/function-count.spec.ts +++ b/test/interpreter/function-count.spec.ts @@ -57,4 +57,19 @@ describe('COUNT', () => { expect(engine.getCellValue(adr('A3'))).toEqual(2) }) + + it('should work with explicit error in arg', () => { + const engine = HyperFormula.buildFromArray([ + ['=COUNT(NA())'], + ]) + expect(engine.getCellValue(adr('A1'))).toEqual(0) + }) + + it('should work for empty arg', () => { + const engine = HyperFormula.buildFromArray([ + ['=COUNT(1,)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(2) + }) }) diff --git a/test/interpreter/function-counta.spec.ts b/test/interpreter/function-counta.spec.ts index 040051e38f..4e47631d53 100644 --- a/test/interpreter/function-counta.spec.ts +++ b/test/interpreter/function-counta.spec.ts @@ -58,4 +58,19 @@ describe('COUNTA', () => { expect(engine.getCellValue(adr('A3'))).toEqual(4) }) + + it('should work with explicit error in arg', () => { + const engine = HyperFormula.buildFromArray([ + ['=COUNTA(NA())'], + ]) + expect(engine.getCellValue(adr('A1'))).toEqual(1) + }) + + it('should work for empty arg', () => { + const engine = HyperFormula.buildFromArray([ + ['=COUNTA(1,)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(2) //Compatible with product 2 + }) }) From be603f279367505b7365b6a0b0d592517fba6ba9 Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 17 Sep 2020 22:38:39 +0200 Subject: [PATCH 16/22] doc --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7f70147bb..a7bc7f58fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed bug in concatenation + nullValue. (#495) - Fixed bug when undoing irreversible operation (#502) - Fixed minor issue with CHAR function logic (#510) +- Fixed issues with numeric aggregation functions (#515) ## [0.1.3] - 2020-07-21 From 34a1e8b2ddfb9c709157f38b9f3c3453fae63144 Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 17 Sep 2020 23:40:17 +0200 Subject: [PATCH 17/22] median fix --- src/interpreter/plugin/MedianPlugin.ts | 7 ++++++- test/interpreter/function-median.spec.ts | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/interpreter/plugin/MedianPlugin.ts b/src/interpreter/plugin/MedianPlugin.ts index 078daee3b8..3971942123 100644 --- a/src/interpreter/plugin/MedianPlugin.ts +++ b/src/interpreter/plugin/MedianPlugin.ts @@ -5,7 +5,7 @@ import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ErrorMessage} from '../../error-message' -import {ProcedureAst} from '../../parser' +import {AstNodeType, ProcedureAst} from '../../parser' import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' /** @@ -35,6 +35,11 @@ export class MedianPlugin extends FunctionPlugin { public median(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { return this.runFunction(ast.args, formulaAddress, this.metadata('MEDIAN'), (...args) => { const values: number[] = args.filter((val: InternalScalarValue) => (typeof val === 'number')) + ast.args.forEach((arg) => { + if(arg.type === AstNodeType.EMPTY) { + values.push(0) + } + }) if (values.length === 0) { return new CellError(ErrorType.NUM, ErrorMessage.OneValue) } diff --git a/test/interpreter/function-median.spec.ts b/test/interpreter/function-median.spec.ts index 5f6d062bae..08db7619c0 100644 --- a/test/interpreter/function-median.spec.ts +++ b/test/interpreter/function-median.spec.ts @@ -95,4 +95,14 @@ describe('Function MEDIAN', () => { expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NUM, ErrorMessage.OneValue)) }) + + it('empty args as 0', () => { + const engine = HyperFormula.buildFromArray([ + ['=MEDIAN(1,2,3,,)'], + ['=MEDIAN(,)'] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(1) + expect(engine.getCellValue(adr('A2'))).toEqual(0) + }) }) From 623d725049c0cccad2beb2a4ca5483a093cf45d2 Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 17 Sep 2020 23:44:24 +0200 Subject: [PATCH 18/22] critical test --- src/interpreter/plugin/MedianPlugin.ts | 2 +- test/interpreter/function-countblank.spec.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/interpreter/plugin/MedianPlugin.ts b/src/interpreter/plugin/MedianPlugin.ts index 3971942123..7ad5a33317 100644 --- a/src/interpreter/plugin/MedianPlugin.ts +++ b/src/interpreter/plugin/MedianPlugin.ts @@ -35,7 +35,7 @@ export class MedianPlugin extends FunctionPlugin { public median(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { return this.runFunction(ast.args, formulaAddress, this.metadata('MEDIAN'), (...args) => { const values: number[] = args.filter((val: InternalScalarValue) => (typeof val === 'number')) - ast.args.forEach((arg) => { + ast.args.forEach((arg) => { //ugly but works if(arg.type === AstNodeType.EMPTY) { values.push(0) } diff --git a/test/interpreter/function-countblank.spec.ts b/test/interpreter/function-countblank.spec.ts index f176acfa75..66007be6da 100644 --- a/test/interpreter/function-countblank.spec.ts +++ b/test/interpreter/function-countblank.spec.ts @@ -5,8 +5,12 @@ import {adr, detailedError} from '../testUtils' describe('COUNTBLANK', () => { it('with empty args', () => { - const engine = HyperFormula.buildFromArray([['=COUNTBLANK()']]) + const engine = HyperFormula.buildFromArray([ + ['=COUNTBLANK()'], + ['=COUNTBLANK(,)'] + ]) expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqual(2) }) it('with args', () => { From c817413167a3bb2c0c571f7daf7864f6fb70c339 Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 17 Sep 2020 23:49:33 +0200 Subject: [PATCH 19/22] should be in a separate file --- src/interpreter/plugin/CountBlankPlugin.ts | 38 +++++++++++++++++++ .../plugin/NumericAggregationPlugin.ts | 19 ---------- 2 files changed, 38 insertions(+), 19 deletions(-) create mode 100644 src/interpreter/plugin/CountBlankPlugin.ts diff --git a/src/interpreter/plugin/CountBlankPlugin.ts b/src/interpreter/plugin/CountBlankPlugin.ts new file mode 100644 index 0000000000..dae07b863e --- /dev/null +++ b/src/interpreter/plugin/CountBlankPlugin.ts @@ -0,0 +1,38 @@ +/** + * @license + * Copyright (c) 2020 Handsoncode. All rights reserved. + */ + +import {CellError, EmptyValue, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' +import {ErrorMessage} from '../../error-message' +import {AstNodeType, ProcedureAst} from '../../parser' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' + +/** + * Interpreter plugin containing MEDIAN function + */ +export class CountBlankPlugin extends FunctionPlugin { + + public static implementedFunctions = { + 'COUNTBLANK': { + method: 'countblank', + parameters: [ + {argumentType: ArgumentTypes.SCALAR} + ], + repeatLastArgs: 1, + expandRanges: true, + }, + } + + public countblank(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.metadata('COUNTBLANK'), (...args: InternalScalarValue[]) => { + let counter = 0 + args.forEach((arg) => { + if(arg === EmptyValue) { + counter++ + } + }) + return counter + }) + } +} diff --git a/src/interpreter/plugin/NumericAggregationPlugin.ts b/src/interpreter/plugin/NumericAggregationPlugin.ts index 17b4ab1bee..b74c1aaf38 100644 --- a/src/interpreter/plugin/NumericAggregationPlugin.ts +++ b/src/interpreter/plugin/NumericAggregationPlugin.ts @@ -79,14 +79,6 @@ export class NumericAggregationPlugin extends FunctionPlugin { 'MINA': { method: 'mina', }, - 'COUNTBLANK': { - method: 'countblank', - parameters: [ - {argumentType: ArgumentTypes.SCALAR} - ], - repeatLastArgs: 1, - expandRanges: true, - }, 'COUNT': { method: 'count', }, @@ -123,17 +115,6 @@ export class NumericAggregationPlugin extends FunctionPlugin { return this.reduce(ast, formulaAddress, 0, 'SUMSQ', this.addWithEpsilon, (arg) => arg*arg, strictlyNumbers) } - public countblank(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.metadata('COUNTBLANK'), (...args: InternalScalarValue[]) => { - let counter = 0 - args.forEach((arg) => { - if(arg === EmptyValue) { - counter++ - } - }) - return counter - }) - } /** * Corresponds to MAX(Number1, Number2, ...). From 79d8894ffc7e3e9e4cb40a51d1d43ee88a949eed Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 17 Sep 2020 23:50:18 +0200 Subject: [PATCH 20/22] commits --- src/interpreter/plugin/CountBlankPlugin.ts | 5 ++--- src/interpreter/plugin/NumericAggregationPlugin.ts | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/interpreter/plugin/CountBlankPlugin.ts b/src/interpreter/plugin/CountBlankPlugin.ts index dae07b863e..4728e2c749 100644 --- a/src/interpreter/plugin/CountBlankPlugin.ts +++ b/src/interpreter/plugin/CountBlankPlugin.ts @@ -3,9 +3,8 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {CellError, EmptyValue, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' -import {ErrorMessage} from '../../error-message' -import {AstNodeType, ProcedureAst} from '../../parser' +import {EmptyValue, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' +import {ProcedureAst} from '../../parser' import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' /** diff --git a/src/interpreter/plugin/NumericAggregationPlugin.ts b/src/interpreter/plugin/NumericAggregationPlugin.ts index b74c1aaf38..9030de4ea7 100644 --- a/src/interpreter/plugin/NumericAggregationPlugin.ts +++ b/src/interpreter/plugin/NumericAggregationPlugin.ts @@ -11,7 +11,7 @@ import {Maybe} from '../../Maybe' import {AstNodeType, CellRangeAst, ProcedureAst} from '../../parser' import {coerceBooleanToNumber} from '../ArithmeticHelper' import {SimpleRangeValue} from '../InterpreterValue' -import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' +import {FunctionPlugin} from './FunctionPlugin' import {ColumnRangeAst, RowRangeAst} from '../../parser/Ast' export type BinaryOperation = (left: T, right: T) => T From fad70d23bda8c28fd3018d4097591e6d1c50e9e1 Mon Sep 17 00:00:00 2001 From: izulin Date: Fri, 18 Sep 2020 00:11:31 +0200 Subject: [PATCH 21/22] export --- src/interpreter/plugin/CountBlankPlugin.ts | 2 +- src/interpreter/plugin/index.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/interpreter/plugin/CountBlankPlugin.ts b/src/interpreter/plugin/CountBlankPlugin.ts index 4728e2c749..ff918abd9a 100644 --- a/src/interpreter/plugin/CountBlankPlugin.ts +++ b/src/interpreter/plugin/CountBlankPlugin.ts @@ -3,7 +3,7 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {EmptyValue, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' +import {EmptyValue, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' diff --git a/src/interpreter/plugin/index.ts b/src/interpreter/plugin/index.ts index b26299efb9..0ebe186b79 100644 --- a/src/interpreter/plugin/index.ts +++ b/src/interpreter/plugin/index.ts @@ -38,3 +38,4 @@ export {CodePlugin} from './CodePlugin' export {ErrorFunctionPlugin} from './ErrorFunctionPlugin' export {CorrelPlugin} from './CorrelPlugin' export {FormulaTextPlugin} from './FormulaTextPlugin' +export {CountBlankPlugin} from './CountBlankPlugin' From 8cd2c095605f454c809cfc5b28cb35cd4cb84c5f Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Fri, 18 Sep 2020 13:22:18 +0200 Subject: [PATCH 22/22] Make it slightly more readable --- src/interpreter/plugin/MedianPlugin.ts | 10 +-- .../plugin/NumericAggregationPlugin.ts | 69 ++++++++++--------- 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/src/interpreter/plugin/MedianPlugin.ts b/src/interpreter/plugin/MedianPlugin.ts index 7ad5a33317..4c0aa15f3e 100644 --- a/src/interpreter/plugin/MedianPlugin.ts +++ b/src/interpreter/plugin/MedianPlugin.ts @@ -17,10 +17,10 @@ export class MedianPlugin extends FunctionPlugin { 'MEDIAN': { method: 'median', parameters: [ - {argumentType: ArgumentTypes.NOERROR}, - ], - repeatLastArgs: 1, - expandRanges: true, + {argumentType: ArgumentTypes.NOERROR}, + ], + repeatLastArgs: 1, + expandRanges: true, }, } @@ -36,7 +36,7 @@ export class MedianPlugin extends FunctionPlugin { return this.runFunction(ast.args, formulaAddress, this.metadata('MEDIAN'), (...args) => { const values: number[] = args.filter((val: InternalScalarValue) => (typeof val === 'number')) ast.args.forEach((arg) => { //ugly but works - if(arg.type === AstNodeType.EMPTY) { + if (arg.type === AstNodeType.EMPTY) { values.push(0) } }) diff --git a/src/interpreter/plugin/NumericAggregationPlugin.ts b/src/interpreter/plugin/NumericAggregationPlugin.ts index 9030de4ea7..20e6e36abf 100644 --- a/src/interpreter/plugin/NumericAggregationPlugin.ts +++ b/src/interpreter/plugin/NumericAggregationPlugin.ts @@ -20,7 +20,7 @@ export type MapOperation = (arg: number) => T type coercionOperation = (arg: InternalScalarValue) => Maybe -function idMap(arg: number): number { +function identityMap(arg: number): number { return arg } @@ -105,14 +105,14 @@ export class NumericAggregationPlugin extends FunctionPlugin { if (ast.args.length < 1) { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } - return this.reduce(ast, formulaAddress, 0, 'SUM', this.addWithEpsilon, idMap, strictlyNumbers) + return this.reduce(ast, formulaAddress, 0, 'SUM', this.addWithEpsilon, identityMap, strictlyNumbers) } public sumsq(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { if (ast.args.length < 1) { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } - return this.reduce(ast, formulaAddress, 0, 'SUMSQ', this.addWithEpsilon, (arg) => arg*arg, strictlyNumbers) + return this.reduce(ast, formulaAddress, 0, 'SUMSQ', this.addWithEpsilon, (arg) => arg * arg, strictlyNumbers) } @@ -130,7 +130,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { } const value = this.reduce(ast, formulaAddress, Number.NEGATIVE_INFINITY, 'MAX', (left, right) => Math.max(left, right), - idMap, strictlyNumbers) + identityMap, strictlyNumbers) return zeroForInfinite(value) } @@ -141,7 +141,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { } const value = this.reduce(ast, formulaAddress, Number.NEGATIVE_INFINITY, 'MAXA', (left, right) => Math.max(left, right), - idMap, numbersBooleans) + identityMap, numbersBooleans) return zeroForInfinite(value) } @@ -160,7 +160,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { } const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MIN', (left, right) => Math.min(left, right), - idMap, strictlyNumbers) + identityMap, strictlyNumbers) return zeroForInfinite(value) } @@ -171,7 +171,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { } const value = this.reduce(ast, formulaAddress, Number.POSITIVE_INFINITY, 'MINA', (left, right) => Math.min(left, right), - idMap, numbersBooleans) + identityMap, numbersBooleans) return zeroForInfinite(value) } @@ -181,8 +181,8 @@ export class NumericAggregationPlugin extends FunctionPlugin { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } const value = this.reduce(ast, formulaAddress, 0, 'COUNT', - (left, right) => left + right, idMap, - (arg) => (typeof arg === 'number') ? 1 : 0 + (left, right) => left + right, identityMap, + (arg) => (typeof arg === 'number') ? 1 : 0 ) return value @@ -193,7 +193,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber) } const value = this.reduce(ast, formulaAddress, 0, 'COUNTA', (left, right) => left + right, - idMap, + identityMap, (arg) => (arg === EmptyValue) ? 0 : 1 ) @@ -206,7 +206,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { } const result = this.reduce(ast, formulaAddress, AverageResult.empty, 'AVERAGE', (left, right) => { return left.compose(right) - }, (arg): AverageResult => { + }, (arg): AverageResult => { return AverageResult.single(arg) }, strictlyNumbers @@ -225,8 +225,8 @@ export class NumericAggregationPlugin extends FunctionPlugin { } const result = this.reduce(ast, formulaAddress, AverageResult.empty, 'AVERAGE', (left, right) => left.compose(right), - (arg): AverageResult => AverageResult.single(arg), - numbersBooleans + (arg): AverageResult => AverageResult.single(arg), + numbersBooleans ) if (result instanceof CellError) { @@ -251,7 +251,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { * */ private reduce(ast: ProcedureAst, formulaAddress: SimpleCellAddress, initialAccValue: T, functionName: string, reducingFunction: BinaryOperation, mapFunction: MapOperation, coercionFunction: coercionOperation): CellError | T { return ast.args.reduce((acc: T | CellError, arg) => { - if(acc instanceof CellError) { + if (acc instanceof CellError) { return acc } if (arg.type === AstNodeType.CELL_RANGE || arg.type === AstNodeType.COLUMN_RANGE || arg.type === AstNodeType.ROW_RANGE) { @@ -260,28 +260,28 @@ export class NumericAggregationPlugin extends FunctionPlugin { let value value = this.evaluateAst(arg, formulaAddress) if (value instanceof SimpleRangeValue) { - return (Array.from(value.valuesFromTopLeftCorner()) + const coercedRangeValues = Array.from(value.valuesFromTopLeftCorner()) .map(coercionFunction) - .filter((arg) => (arg !== undefined)) as (CellError | number)[]) + .filter((arg) => (arg !== undefined)) as (CellError | number)[] + + return coercedRangeValues .map((arg) => { - if(arg instanceof CellError){ + if (arg instanceof CellError) { return arg } else { return mapFunction(arg) } }) .reduce((left, right) => { - if(left instanceof CellError) { + if (left instanceof CellError) { return left - } else if(right instanceof CellError) { + } else if (right instanceof CellError) { return right } else { return reducingFunction(left, right) } }, acc) - } - - if (arg.type === AstNodeType.CELL_REFERENCE) { + } else if (arg.type === AstNodeType.CELL_REFERENCE) { value = coercionFunction(value) if (value === undefined) { return acc @@ -293,7 +293,8 @@ export class NumericAggregationPlugin extends FunctionPlugin { return acc } } - if(value instanceof CellError) { + + if (value instanceof CellError) { return value } @@ -330,10 +331,10 @@ export class NumericAggregationPlugin extends FunctionPlugin { let value = rangeVertex.getFunctionValue(functionName) as (T | CellError) if (!value) { const rangeValues = this.getRangeValues(functionName, range, mapFunction, coercionFunction) - value = rangeValues.reduce( (arg1, arg2) => { - if(arg1 instanceof CellError) { + value = rangeValues.reduce((arg1, arg2) => { + if (arg1 instanceof CellError) { return arg1 - } else if(arg2 instanceof CellError) { + } else if (arg2 instanceof CellError) { return arg2 } else { return reducingFunction(arg1, arg2) @@ -366,9 +367,9 @@ export class NumericAggregationPlugin extends FunctionPlugin { } else { for (const cellFromRange of smallerRangeVertex.range.addresses(this.dependencyGraph)) { const val = coercionFunction(this.dependencyGraph.getScalarValue(cellFromRange)) - if(val instanceof CellError) { + if (val instanceof CellError) { rangeResult.push(val) - } else if(val !== undefined) { + } else if (val !== undefined) { rangeResult.push(mapFunction(val)) } } @@ -379,9 +380,9 @@ export class NumericAggregationPlugin extends FunctionPlugin { } for (const cellFromRange of actualRange.addresses(this.dependencyGraph)) { const val = coercionFunction(this.dependencyGraph.getScalarValue(cellFromRange)) - if(val instanceof CellError) { + if (val instanceof CellError) { rangeResult.push(val) - } else if(val !== undefined) { + } else if (val !== undefined) { rangeResult.push(mapFunction(val)) } } @@ -391,7 +392,7 @@ export class NumericAggregationPlugin extends FunctionPlugin { } function strictlyNumbers(arg: InternalScalarValue): Maybe { - if(typeof arg === 'number' || arg instanceof CellError) { + if (typeof arg === 'number' || arg instanceof CellError) { return arg } else { return undefined @@ -399,11 +400,11 @@ function strictlyNumbers(arg: InternalScalarValue): Maybe { } function numbersBooleans(arg: InternalScalarValue): Maybe { - if(typeof arg === 'boolean') { + if (typeof arg === 'boolean') { return coerceBooleanToNumber(arg) - } else if(typeof arg === 'number' || arg instanceof CellError) { + } else if (typeof arg === 'number' || arg instanceof CellError) { return arg - } else if(typeof arg === 'string') { + } else if (typeof arg === 'string') { return 0 } else { return undefined