From 74ecd395081dfc527c3c6be4bad85194ad0c1c65 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Fri, 26 Jun 2020 14:54:43 +0200 Subject: [PATCH 01/97] LEN function --- src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + src/interpreter/plugin/TextPlugin.ts | 9 +++++++ test/interpreter/function-len.spec.ts | 34 +++++++++++++++++++++++++++ 18 files changed, 59 insertions(+) create mode 100644 test/interpreter/function-len.spec.ts diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index cde62afc16..1539f49ad3 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -80,6 +80,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'JE.ČISLO', ISODD: 'ISODD', ISTEXT: 'JE.TEXT', + LEN: 'DÉLKA', LN: 'LN', LOG: 'LOGZ', LOG10: 'LOG', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 588c11e082..940b33b55b 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -80,6 +80,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ER.TAL', ISODD: 'ER.ULIGE', ISTEXT: 'ER.TEKST', + LEN: 'LÆNGDE', LN: 'LN', LOG: 'LOG', LOG10: 'LOG10', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index 6fba432733..bbe2a02033 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -80,6 +80,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ISTZAHL', ISODD: 'ISTUNGERADE', ISTEXT: 'ISTTEXT', + LEN: 'LÄNGE', LN: 'LN', LOG: 'LOG', LOG10: 'LOG10', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index 6d5c7f51b0..0837ae90c2 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -80,6 +80,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ISNUMBER', ISODD: 'ISODD', ISTEXT: 'ISTEXT', + LEN: 'LEN', LN: 'LN', LOG: 'LOG', LOG10: 'LOG10', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index 5ade8c54e5..999cb051a3 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -80,6 +80,7 @@ export const dictionary: RawTranslationPackage = { ISNUMBER: 'ESNUMERO', ISODD: 'ES.IMPAR', ISTEXT: 'ESTEXTO', + LEN: 'LARGO', LN: 'LN', LOG: 'LOG', LOG10: 'LOG10', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index c46d11d424..d36048078e 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -80,6 +80,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ONLUKU', ISODD: 'ONPARITON', ISTEXT: 'ONTEKSTI', + LEN: 'PITUUS', LN: 'LUONNLOG', LOG: 'LOG', LOG10: 'LOG10', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index 7befe0f217..6591373775 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -80,6 +80,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ESTNUM', ISODD: 'EST.IMPAIR', ISTEXT: 'ESTTEXTE', + LEN: 'NBCAR', LN: 'LN', LOG: 'LOG', LOG10: 'LOG10', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index 0f72282b01..bdad0c7736 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -80,6 +80,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'SZÁM', ISODD: 'PÁRATLANE', ISTEXT: 'SZÖVEG.E', + LEN: 'HOSSZ', LN: 'LN', LOG: 'LOG', LOG10: 'LOG10', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index 166b82e7bc..67ad42ad44 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -80,6 +80,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'VAL.NUMERO', ISODD: 'VAL.DISPARI', ISTEXT: 'VAL.TESTO', + LEN: 'LUNGHEZZA', LN: 'LN', LOG: 'LOG', LOG10: 'LOG10', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index 0294e4f404..ae807d8e15 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -80,6 +80,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ERTALL', ISODD: 'ERODDE', ISTEXT: 'ERTEKST', + LEN: 'LENGDE', LN: 'LN', LOG: 'LOG', LOG10: 'LOG10', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index 7de90f72a4..b9b774da69 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -80,6 +80,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ISGETAL', ISODD: 'IS.ONEVEN', ISTEXT: 'ISTEKST', + LEN: 'PITUUS', LN: 'LN', LOG: 'LOG', LOG10: 'LOG10', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index 96bb78e8af..f7f6426ed2 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -80,6 +80,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'CZY.LICZBA', ISODD: 'CZY.NIEPARZYSTE', ISTEXT: 'CZY.TEKST', + LEN: 'DŁ', LN: 'LN', LOG: 'LOG', LOG10: 'LOG10', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index c6a484678d..d96baea476 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -80,6 +80,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ÉNÚM', ISODD: 'ÉIMPAR', ISTEXT: 'ÉTEXTO', + LEN: 'NÚM.CARACT', LN: 'LN', LOG: 'LOG', LOG10: 'LOG10', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index 8b1d05c5df..ea0edf0202 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -80,6 +80,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ЕЧИСЛО', ISODD: 'ЕНЕЧЁТ', ISTEXT: 'ЕТЕКСТ', + LEN: 'ДЛСТР', LN: 'LN', LOG: 'LOG', LOG10: 'LOG10', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index 0cdca3717e..d27f2ffaed 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -80,6 +80,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ÄRTAL', ISODD: 'ÄRUDDA', ISTEXT: 'ÄRTEXT', + LEN: 'LÄNGD', LN: 'LN', LOG: 'LOG', LOG10: 'LOG10', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index 439fe14ec3..e88bc447a7 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -80,6 +80,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ESAYIYSA', ISODD: 'TEKMİ', ISTEXT: 'EMETİNSE', + LEN: 'UZUNLUK', LN: 'LN', LOG: 'LOG', LOG10: 'LOG10', diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index 475c5afb1f..0985125116 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -19,6 +19,9 @@ export class TextPlugin extends FunctionPlugin { 'SPLIT': { method: 'split', }, + 'LEN': { + method: 'len' + } } /** @@ -84,4 +87,10 @@ export class TextPlugin extends FunctionPlugin { return splittedString[indexToUse] } + + public len(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.templateWithOneCoercedToStringArgument(ast, formulaAddress, (arg: string) => { + return arg.length + }) + } } diff --git a/test/interpreter/function-len.spec.ts b/test/interpreter/function-len.spec.ts new file mode 100644 index 0000000000..f15b408163 --- /dev/null +++ b/test/interpreter/function-len.spec.ts @@ -0,0 +1,34 @@ +import {ErrorType, HyperFormula} from '../../src' +import {adr, detailedError} from '../testUtils' + +describe('Function LEN', () => { + it('should return N/A when number of arguments is incorrect', () => { + const engine = HyperFormula.buildFromArray([ + ['=LEN()'], + ['=LEN("foo", "bar")'] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NA)) + expect(engine.getCellValue(adr('A2'))).toEqual(detailedError(ErrorType.NA)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=LEN("foo")'] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(3) + }) + + it('should coerce other types to string', () => { + const engine = HyperFormula.buildFromArray([ + ['=LEN(1)'], + ['=LEN(5+5)'], + ['=LEN(TRUE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(1) + expect(engine.getCellValue(adr('A2'))).toEqual(2) + expect(engine.getCellValue(adr('A3'))).toEqual(4) + }) +}) \ No newline at end of file From d202b51e3ca58e7e92f792879eb00d8fa9020aa6 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Fri, 26 Jun 2020 15:40:05 +0200 Subject: [PATCH 02/97] TRIM function --- src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + src/interpreter/plugin/TextPlugin.ts | 9 ++++++ test/interpreter/function-trim.spec.ts | 40 ++++++++++++++++++++++++++ 18 files changed, 65 insertions(+) create mode 100644 test/interpreter/function-trim.spec.ts diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index 1539f49ad3..39dae1dafe 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -119,6 +119,7 @@ const dictionary: RawTranslationPackage = { TAN: 'TG', TEXT: 'HODNOTA.NA.TEXT', TRANSPOSE: 'TRANSPOZICE', + TRIM: 'PROČISTIT', TRUE: 'PRAVDA', TRUNC: 'USEKNOUT', VLOOKUP: 'SVYHLEDAT', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 940b33b55b..ad9f6125bd 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -119,6 +119,7 @@ const dictionary: RawTranslationPackage = { TAN: 'TAN', TEXT: 'TEKST', TRANSPOSE: 'TRANSPONER', + TRIM: 'FJERN.OVERFLØDIGE.BLANKE', TRUE: 'TRUE', TRUNC: 'AFKORT', VLOOKUP: 'LOPSLAG', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index bbe2a02033..a636670d93 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -119,6 +119,7 @@ const dictionary: RawTranslationPackage = { TAN: 'TAN', TEXT: 'TEXT', TRANSPOSE: 'MTRANS', + TRIM: 'GLÄTTEN', TRUE: 'WAHR', TRUNC: 'KÜRZEN', VLOOKUP: 'SVERWEIS', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index 0837ae90c2..9a1c03038a 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -119,6 +119,7 @@ const dictionary: RawTranslationPackage = { TAN: 'TAN', TEXT: 'TEXT', TRANSPOSE: 'TRANSPOSE', + TRIM: 'TRIM', TRUE: 'TRUE', TRUNC: 'TRUNC', VLOOKUP: 'VLOOKUP', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index 999cb051a3..6ef0c55e28 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -119,6 +119,7 @@ export const dictionary: RawTranslationPackage = { TAN: 'TAN', TEXT: 'TEXTO', TRANSPOSE: 'TRANSPONER', + TRIM: 'ESPACIOS', TRUE: 'VERDADERO', TRUNC: 'TRUNCAR', VLOOKUP: 'BUSCARV', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index d36048078e..678e739a9c 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -119,6 +119,7 @@ const dictionary: RawTranslationPackage = { TAN: 'TAN', TEXT: 'TEKSTI', TRANSPOSE: 'TRANSPONOI', + TRIM: 'POISTA.VÄLIT', TRUE: 'TOSI', TRUNC: 'KATKAISE', VLOOKUP: 'PHAKU', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index 6591373775..1901c03b0b 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -119,6 +119,7 @@ const dictionary: RawTranslationPackage = { TAN: 'TAN', TEXT: 'TEXTE', TRANSPOSE: 'TRANSPOSE', + TRIM: 'SUPPRESPACE', TRUE: 'VRAI', TRUNC: 'TRONQUE', VLOOKUP: 'RECHERCHEV', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index bdad0c7736..99b75c7706 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -119,6 +119,7 @@ const dictionary: RawTranslationPackage = { TAN: 'TAN', TEXT: 'SZÖVEG', TRANSPOSE: 'TRANSZPONÁLÁS', + TRIM: 'KIMETSZ', TRUE: 'IGAZ', TRUNC: 'CSONK', VLOOKUP: 'FKERES', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index 67ad42ad44..8b7f37d3a3 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -119,6 +119,7 @@ const dictionary: RawTranslationPackage = { TAN: 'TAN', TEXT: 'TESTO', TRANSPOSE: 'MATR.TRASPOSTA', + TRIM: 'ANNULLA.SPAZI', TRUE: 'VERO', TRUNC: 'TRONCA', VLOOKUP: 'CERCA.VERT', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index ae807d8e15..ae104a2efc 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -119,6 +119,7 @@ const dictionary: RawTranslationPackage = { TAN: 'TAN', TEXT: 'TEKST', TRANSPOSE: 'TRANSPONER', + TRIM: 'TRIMME', TRUE: 'SANN', TRUNC: 'AVKORT', VLOOKUP: 'FINN.RAD', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index b9b774da69..fa2415880e 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -119,6 +119,7 @@ const dictionary: RawTranslationPackage = { TAN: 'TAN', TEXT: 'TEKST', TRANSPOSE: 'TRANSPONEREN', + TRIM: 'SPATIES.WISSEN', TRUE: 'WAAR', TRUNC: 'GEHEEL', VLOOKUP: 'VERT.ZOEKEN', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index f7f6426ed2..bcd18afcd4 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -119,6 +119,7 @@ const dictionary: RawTranslationPackage = { TAN: 'TAN', TEXT: 'TEKST', TRANSPOSE: 'TRANSPONUJ', + TRIM: 'USUŃ.ZBĘDNE.ODSTĘPY', TRUE: 'PRAWDA', TRUNC: 'LICZBA.CAŁK', VLOOKUP: 'WYSZUKAJ.PIONOWO', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index d96baea476..ef8540ef26 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -119,6 +119,7 @@ const dictionary: RawTranslationPackage = { TAN: 'TAN', TEXT: 'TEXTO', TRANSPOSE: 'TRANSPOR', + TRIM: 'ARRUMAR', TRUE: 'VERDADEIRO', TRUNC: 'TRUNCAR', VLOOKUP: 'PROCV', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index ea0edf0202..b302d70876 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -119,6 +119,7 @@ const dictionary: RawTranslationPackage = { TAN: 'TAN', TEXT: 'ТЕКСТ', TRANSPOSE: 'ТРАНСП', + TRIM: 'СЖПРОБЕЛЫ', TRUE: 'ИСТИНА', TRUNC: 'ОТБР', VLOOKUP: 'ВПР', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index d27f2ffaed..1a3d4068d4 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -119,6 +119,7 @@ const dictionary: RawTranslationPackage = { TAN: 'TAN', TEXT: 'TEXT', TRANSPOSE: 'TRANSPONERA', + TRIM: 'RENSA', TRUE: 'SANT', TRUNC: 'AVKORTA', VLOOKUP: 'LETARAD', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index e88bc447a7..b65d012e51 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -119,6 +119,7 @@ const dictionary: RawTranslationPackage = { TAN: 'TAN', TEXT: 'METNEÇEVİR', TRANSPOSE: 'DEVRİK_DÖNÜŞÜM', + TRIM: 'KIRP', TRUE: 'DOĞRU', TRUNC: 'NSAT', VLOOKUP: 'DÜŞEYARA', diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index 0985125116..4fb0a97e62 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -21,6 +21,9 @@ export class TextPlugin extends FunctionPlugin { }, 'LEN': { method: 'len' + }, + 'TRIM': { + method: 'trim' } } @@ -93,4 +96,10 @@ export class TextPlugin extends FunctionPlugin { return arg.length }) } + + public trim(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.templateWithOneCoercedToStringArgument(ast, formulaAddress, (arg: string) => { + return arg.replace(/^ +| +$/g, '').replace(/ +/g, ' ') + }) + } } diff --git a/test/interpreter/function-trim.spec.ts b/test/interpreter/function-trim.spec.ts new file mode 100644 index 0000000000..520beb9a41 --- /dev/null +++ b/test/interpreter/function-trim.spec.ts @@ -0,0 +1,40 @@ +import {ErrorType, HyperFormula} from '../../src' +import {adr, detailedError} from '../testUtils' + +describe('Function TRIM', () => { + it('should return N/A when number of arguments is incorrect', () => { + const engine = HyperFormula.buildFromArray([ + ['=TRIM()'], + ['=TRIM("foo", "bar")'] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NA)) + expect(engine.getCellValue(adr('A2'))).toEqual(detailedError(ErrorType.NA)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=TRIM(" foo")'], + ['=TRIM("foo ")'], + ['=TRIM(" foo ")'], + ['=TRIM(" f o o ")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual('foo') + expect(engine.getCellValue(adr('A2'))).toEqual('foo') + expect(engine.getCellValue(adr('A3'))).toEqual('foo') + expect(engine.getCellValue(adr('A4'))).toEqual('f o o') + }) + + it('should coerce other types to string', () => { + const engine = HyperFormula.buildFromArray([ + ['=TRIM(1)'], + ['=TRIM(5+5)'], + ['=TRIM(TRUE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual('1') + expect(engine.getCellValue(adr('A2'))).toEqual('10') + expect(engine.getCellValue(adr('A3'))).toEqual('TRUE') + }) +}) \ No newline at end of file From 04b2938bcca69225c331291f8030d627c1541c08 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Fri, 26 Jun 2020 15:58:06 +0200 Subject: [PATCH 03/97] PROPER function --- src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + src/interpreter/plugin/TextPlugin.ts | 9 ++++++ test/interpreter/function-proper.spec.ts | 40 ++++++++++++++++++++++++ 18 files changed, 65 insertions(+) create mode 100644 test/interpreter/function-proper.spec.ts diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index 39dae1dafe..b538ad0f96 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -101,6 +101,7 @@ const dictionary: RawTranslationPackage = { OR: 'NEBO', PI: 'PI', POWER: 'POWER', + PROPER: 'VELKÁ2', RADIANS: 'RADIANS', RAND: 'NÁHČÍSLO', ROUND: 'ZAOKROUHLIT', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index ad9f6125bd..3d1dd7acfa 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -101,6 +101,7 @@ const dictionary: RawTranslationPackage = { OR: 'ELLER', PI: 'PI', POWER: 'POTENS', + PROPER: 'STORT.FORBOGSTAV', RADIANS: 'RADIANER', RAND: 'SLUMP', ROUND: 'AFRUND', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index a636670d93..87d7532ac0 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -101,6 +101,7 @@ const dictionary: RawTranslationPackage = { OR: 'ODER', PI: 'PI', POWER: 'POTENZ', + PROPER: 'GROSS2', RADIANS: 'BOGENMASS', RAND: 'ZUFALLSZAHL', ROUND: 'RUNDEN', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index 9a1c03038a..f1356aae78 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -101,6 +101,7 @@ const dictionary: RawTranslationPackage = { OR: 'OR', PI: 'PI', POWER: 'POWER', + PROPER: 'PROPER', RADIANS: 'RADIANS', RAND: 'RAND', ROUND: 'ROUND', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index 6ef0c55e28..8d34609374 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -101,6 +101,7 @@ export const dictionary: RawTranslationPackage = { OR: 'O', PI: 'PI', POWER: 'POTENCIA', + PROPER: 'NOMPROPIO', RADIANS: 'RADIANES', RAND: 'ALEATORIO', ROUND: 'REDONDEAR', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 678e739a9c..fdf70fc27f 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -101,6 +101,7 @@ const dictionary: RawTranslationPackage = { OR: 'TAI', PI: 'PII', POWER: 'POTENSSI', + PROPER: 'ERISNIMI', RADIANS: 'RADIAANIT', RAND: 'SATUNNAISLUKU', ROUND: 'PYÖRISTÄ', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index 1901c03b0b..5a86324768 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -101,6 +101,7 @@ const dictionary: RawTranslationPackage = { OR: 'OU', PI: 'PI', POWER: 'PUISSANCE', + PROPER: 'NOMPROPRE', RADIANS: 'RADIANS', RAND: 'ALEA', ROUND: 'ARRONDI', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index 99b75c7706..af29147d87 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -101,6 +101,7 @@ const dictionary: RawTranslationPackage = { OR: 'VAGY', PI: 'PI', POWER: 'HATVÁNY', + PROPER: 'TNÉV', RADIANS: 'RADIÁN', RAND: 'VÉL', ROUND: 'KEREKÍTÉS', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index 8b7f37d3a3..d0d693746e 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -101,6 +101,7 @@ const dictionary: RawTranslationPackage = { OR: 'O', PI: 'PI.GRECO', POWER: 'POTENZA', + PROPER: 'MAIUSC.INIZ', RADIANS: 'RADIANTI', RAND: 'CASUALE', ROUND: 'ARROTONDA', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index ae104a2efc..0c877ea383 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -101,6 +101,7 @@ const dictionary: RawTranslationPackage = { OR: 'ELLER', PI: 'PI', POWER: 'OPPHØYD.I', + PROPER: 'STOR.FORBOKSTAV', RADIANS: 'RADIANER', RAND: 'TILFELDIG', ROUND: 'AVRUND', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index fa2415880e..8c333759c7 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -101,6 +101,7 @@ const dictionary: RawTranslationPackage = { OR: 'OF', PI: 'PI', POWER: 'MACHT', + PROPER: 'BEGINLETTERS', RADIANS: 'RADIALEN', RAND: 'ASELECT', ROUND: 'AFRONDEN', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index bcd18afcd4..44dec549d4 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -101,6 +101,7 @@ const dictionary: RawTranslationPackage = { OR: 'LUB', PI: 'PI', POWER: 'POTĘGA', + PROPER: 'Z.WIELKIEJ.LITERY', RADIANS: 'RADIANY', RAND: 'LOSUJ', ROUND: 'ZAOKR', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index ef8540ef26..fbcb04821f 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -101,6 +101,7 @@ const dictionary: RawTranslationPackage = { OR: 'OU', PI: 'PI', POWER: 'POTÊNCIA', + PROPER: 'PRI.MAIÚSCULA', RADIANS: 'RADIANOS', RAND: 'ALEATÓRIO', ROUND: 'ARRED', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index b302d70876..c5daf7eb81 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -101,6 +101,7 @@ const dictionary: RawTranslationPackage = { OR: 'ИЛИ', PI: 'ПИ', POWER: 'СТЕПЕНЬ', + PROPER: 'ПРОПНАЧ', RADIANS: 'РАДИАНЫ', RAND: 'СЛЧИС', ROUND: 'ОКРУГЛ', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index 1a3d4068d4..48887de6da 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -101,6 +101,7 @@ const dictionary: RawTranslationPackage = { OR: 'ELLER', PI: 'PI', POWER: 'UPPHÖJT.TILL', + PROPER: 'INITIAL', RADIANS: 'RADIANER', RAND: 'SLUMP', ROUND: 'AVRUNDA', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index b65d012e51..a761d88782 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -101,6 +101,7 @@ const dictionary: RawTranslationPackage = { OR: 'VEYA', PI: 'Pİ', POWER: 'KUVVET', + PROPER: 'YAZIM.DÜZENİ', RADIANS: 'RADYAN', RAND: 'S_SAYI_ÜRET', ROUND: 'YUVARLA', diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index 4fb0a97e62..e16f393050 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -24,6 +24,9 @@ export class TextPlugin extends FunctionPlugin { }, 'TRIM': { method: 'trim' + }, + 'PROPER': { + method: 'proper' } } @@ -102,4 +105,10 @@ export class TextPlugin extends FunctionPlugin { return arg.replace(/^ +| +$/g, '').replace(/ +/g, ' ') }) } + + public proper(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.templateWithOneCoercedToStringArgument(ast, formulaAddress, (arg: string) => { + return arg.replace(/\w\S*/g, word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase()) + }) + } } diff --git a/test/interpreter/function-proper.spec.ts b/test/interpreter/function-proper.spec.ts new file mode 100644 index 0000000000..0765b6f441 --- /dev/null +++ b/test/interpreter/function-proper.spec.ts @@ -0,0 +1,40 @@ +import {ErrorType, HyperFormula} from '../../src' +import {adr, detailedError} from '../testUtils' + +describe('Function PROPER', () => { + it('should return N/A when number of arguments is incorrect', () => { + const engine = HyperFormula.buildFromArray([ + ['=PROPER()'], + ['=PROPER("foo", "bar")'] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NA)) + expect(engine.getCellValue(adr('A2'))).toEqual(detailedError(ErrorType.NA)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=PROPER("foo")'], + ['=PROPER("foo bar")'], + ['=PROPER(" foo bar ")'], + ['=PROPER("fOo BAR")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual('Foo') + expect(engine.getCellValue(adr('A2'))).toEqual('Foo Bar') + expect(engine.getCellValue(adr('A3'))).toEqual(' Foo Bar ') + expect(engine.getCellValue(adr('A4'))).toEqual('Foo Bar') + }) + + it('should coerce other types to string', () => { + const engine = HyperFormula.buildFromArray([ + ['=PROPER(1)'], + ['=PROPER(5+5)'], + ['=PROPER(TRUE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual('1') + expect(engine.getCellValue(adr('A2'))).toEqual('10') + expect(engine.getCellValue(adr('A3'))).toEqual('True') + }) +}) \ No newline at end of file From b934922fe66fafe2d4848014aea7e6e601225771 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Sat, 27 Jun 2020 10:42:27 +0200 Subject: [PATCH 04/97] CLEAN function --- src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + src/interpreter/plugin/TextPlugin.ts | 10 ++++++ test/interpreter/function-clean.spec.ts | 47 +++++++++++++++++++++++++ 18 files changed, 73 insertions(+) create mode 100644 test/interpreter/function-clean.spec.ts diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index b538ad0f96..30a963ed82 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -39,6 +39,7 @@ const dictionary: RawTranslationPackage = { CEILING: 'ZAOKR.NAHORU', CHAR: 'ZNAK', CHOOSE: 'ZVOLIT', + CLEAN: 'VYČISTIT', CODE: 'KÓD', COLUMNS: 'SLOUPCE', CONCATENATE: 'CONCATENATE', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 3d1dd7acfa..74e80729fe 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -39,6 +39,7 @@ const dictionary: RawTranslationPackage = { CEILING: 'AFRUND.LOFT', CHAR: 'CHAR', CHOOSE: 'VÆLG', + CLEAN: 'RENS', CODE: 'KODE', COLUMNS: 'KOLONNER', CONCATENATE: 'SAMMENKÆDNING', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index 87d7532ac0..a62ad0105d 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -39,6 +39,7 @@ const dictionary: RawTranslationPackage = { CEILING: 'OBERGRENZE', CHAR: 'ZEICHEN', CHOOSE: 'WAHL', + CLEAN: 'SÄUBERN', CODE: 'CODE', COLUMNS: 'SPALTEN', CONCATENATE: 'VERKETTEN', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index f1356aae78..e2cd117605 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -39,6 +39,7 @@ const dictionary: RawTranslationPackage = { CEILING: 'CEILING', CHAR: 'CHAR', CHOOSE: 'CHOOSE', + CLEAN: 'CLEAN', CODE: 'CODE', COLUMNS: 'COLUMNS', CONCATENATE: 'CONCATENATE', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index 8d34609374..08b15e2d8b 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -39,6 +39,7 @@ export const dictionary: RawTranslationPackage = { CEILING: 'MULTIPLO.SUPERIOR', CHAR: 'CARACTER', CHOOSE: 'ELEGIR', + CLEAN: 'LIMPIAR', CODE: 'CODIGO', COLUMNS: 'COLUMNAS', CONCATENATE: 'CONCATENAR', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index fdf70fc27f..e7180e596e 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -39,6 +39,7 @@ const dictionary: RawTranslationPackage = { CEILING: 'PYÖRISTÄ.KERR.YLÖS', CHAR: 'MERKKI', CHOOSE: 'VALITSE.INDEKSI', + CLEAN: 'SIIVOA', CODE: 'KOODI', COLUMNS: 'SARAKKEET', CONCATENATE: 'KETJUTA', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index 5a86324768..205d942c34 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -39,6 +39,7 @@ const dictionary: RawTranslationPackage = { CEILING: 'PLAFOND', CHAR: 'CAR', CHOOSE: 'CHOISIR', + CLEAN: 'EPURAGE', CODE: 'CODE', COLUMNS: 'COLONNES', CONCATENATE: 'CONCATENER', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index af29147d87..aa3a19b0ce 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -39,6 +39,7 @@ const dictionary: RawTranslationPackage = { CEILING: 'PLAFON', CHAR: 'KARAKTER', CHOOSE: 'VÁLASZT', + CLEAN: 'TISZTÍT', CODE: 'KÓD', COLUMNS: 'OSZLOPOK', CONCATENATE: 'ÖSSZEFŰZ', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index d0d693746e..f9d194c60b 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -39,6 +39,7 @@ const dictionary: RawTranslationPackage = { CEILING: 'ARROTONDA.ECCESSO', CHAR: 'CODICE.CARATT', CHOOSE: 'SCEGLI', + CLEAN: 'LIBERA', CODE: 'CODICE', COLUMNS: 'COLONNE', CONCATENATE: 'CONCATENA', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index 0c877ea383..8537f073b1 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -39,6 +39,7 @@ const dictionary: RawTranslationPackage = { CEILING: 'AVRUND.GJELDENDE.MULTIPLUM', CHAR: 'TEGNKODE', CHOOSE: 'VELG', + CLEAN: 'RENSK', CODE: 'KODE', COLUMNS: 'KOLONNER', CONCATENATE: 'KJEDE.SAMMEN', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index 8c333759c7..48a56f5770 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -39,6 +39,7 @@ const dictionary: RawTranslationPackage = { CEILING: 'AFRONDEN.BOVEN', CHAR: 'TEKEN', CHOOSE: 'KIEZEN', + CLEAN: 'WISSEN.CONTROL', CODE: 'CODE', COLUMNS: 'KOLOMMEN', CONCATENATE: 'TEKST.SAMENVOEGEN', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index 44dec549d4..f840475780 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -39,6 +39,7 @@ const dictionary: RawTranslationPackage = { CEILING: 'ZAOKR.W.GÓRĘ', CHAR: 'ZNAK', CHOOSE: 'WYBIERZ', + CLEAN: 'OCZYŚĆ', CODE: 'KOD', COLUMNS: 'LICZBA.KOLUMN', CONCATENATE: 'ZŁĄCZ.TEKST', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index fbcb04821f..89f0d57865 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -39,6 +39,7 @@ const dictionary: RawTranslationPackage = { CEILING: 'TETO', CHAR: 'CARACT', CHOOSE: 'ESCOLHER', + CLEAN: 'TIRAR', CODE: 'CÓDIGO', COLUMNS: 'COLS', CONCATENATE: 'CONCATENAR', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index c5daf7eb81..82d5bfc1fe 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -39,6 +39,7 @@ const dictionary: RawTranslationPackage = { CEILING: 'ОКРВВЕРХ', CHAR: 'СИМВОЛ', CHOOSE: 'ВЫБОР', + CLEAN: 'ПЕЧСИМВ', CODE: 'КОДСИМВ', COLUMNS: 'ЧИСЛСТОЛБ', CONCATENATE: 'СЦЕПИТЬ', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index 48887de6da..9bdd7953ff 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -39,6 +39,7 @@ const dictionary: RawTranslationPackage = { CEILING: 'RUNDA.UPP', CHAR: 'TECKENKOD', CHOOSE: 'VÄLJ', + CLEAN: 'STÄDA', CODE: 'KOD', COLUMNS: 'KOLUMNER', CONCATENATE: 'SAMMANFOGA', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index a761d88782..0f90a98a9c 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -39,6 +39,7 @@ const dictionary: RawTranslationPackage = { CEILING: 'TAVANAYUVARLA', CHAR: 'DAMGA', CHOOSE: 'ELEMAN', + CLEAN: 'TEMİZ', CODE: 'KOD', COLUMNS: 'SÜTUNSAY', CONCATENATE: 'BİRLEŞTİR', diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index e16f393050..553eedc6e3 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -27,6 +27,9 @@ export class TextPlugin extends FunctionPlugin { }, 'PROPER': { method: 'proper' + }, + 'CLEAN': { + method: 'clean' } } @@ -111,4 +114,11 @@ export class TextPlugin extends FunctionPlugin { return arg.replace(/\w\S*/g, word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase()) }) } + + public clean(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.templateWithOneCoercedToStringArgument(ast, formulaAddress, (arg: string) => { + // eslint-disable-next-line no-control-regex + return arg.replace(/[\u0000-\u001F]/g, '') + }) + } } diff --git a/test/interpreter/function-clean.spec.ts b/test/interpreter/function-clean.spec.ts new file mode 100644 index 0000000000..65333029de --- /dev/null +++ b/test/interpreter/function-clean.spec.ts @@ -0,0 +1,47 @@ +import {ErrorType, HyperFormula} from '../../src' +import {adr, detailedError} from '../testUtils' + +describe('Function CLEAN', () => { + it('should return N/A when number of arguments is incorrect', () => { + const engine = HyperFormula.buildFromArray([ + ['=CLEAN()'], + ['=CLEAN("foo", "bar")'] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NA)) + expect(engine.getCellValue(adr('A2'))).toEqual(detailedError(ErrorType.NA)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=CLEAN("foo\u0000")'], + ['=CLEAN("foo\u0020")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual('foo') + expect(engine.getCellValue(adr('A2'))).toEqual('foo\u0020') + }) + + it('should clean all non-printable ASCII characters', () => { + const str = Array.from(Array(32).keys()).map(code => String.fromCharCode(code)).join('') + + const engine = HyperFormula.buildFromArray([ + [str, '=LEN(A1)', '=CLEAN(A1)'], + ]) + + expect(engine.getCellValue(adr('B1'))).toEqual(32) + expect(engine.getCellValue(adr('C1'))).toEqual('') + }) + + it('should coerce other types to string', () => { + const engine = HyperFormula.buildFromArray([ + ['=CLEAN(1)'], + ['=CLEAN(5+5)'], + ['=CLEAN(TRUE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual('1') + expect(engine.getCellValue(adr('A2'))).toEqual('10') + expect(engine.getCellValue(adr('A3'))).toEqual('TRUE') + }) +}) \ No newline at end of file From e2228f2c3d6fedc66e65502dbc5d10b73f7b8737 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Sat, 27 Jun 2020 12:18:43 +0200 Subject: [PATCH 05/97] REPT function --- src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + src/interpreter/plugin/FunctionPlugin.ts | 45 ++++++++++-------- src/interpreter/plugin/TextPlugin.ts | 15 ++++++ test/interpreter/function-rept.spec.ts | 58 ++++++++++++++++++++++++ 19 files changed, 114 insertions(+), 20 deletions(-) create mode 100644 test/interpreter/function-rept.spec.ts diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index 30a963ed82..1fbd25774a 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -105,6 +105,7 @@ const dictionary: RawTranslationPackage = { PROPER: 'VELKÁ2', RADIANS: 'RADIANS', RAND: 'NÁHČÍSLO', + REPT: 'OPAKOVAT', ROUND: 'ZAOKROUHLIT', ROUNDDOWN: 'ROUNDDOWN', ROUNDUP: 'ROUNDUP', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 74e80729fe..3d51bc5de0 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -105,6 +105,7 @@ const dictionary: RawTranslationPackage = { PROPER: 'STORT.FORBOGSTAV', RADIANS: 'RADIANER', RAND: 'SLUMP', + REPT: 'GENTAG', ROUND: 'AFRUND', ROUNDDOWN: 'RUND.NED', ROUNDUP: 'RUND.OP', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index a62ad0105d..ff146b71e4 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -105,6 +105,7 @@ const dictionary: RawTranslationPackage = { PROPER: 'GROSS2', RADIANS: 'BOGENMASS', RAND: 'ZUFALLSZAHL', + REPT: 'WIEDERHOLEN', ROUND: 'RUNDEN', ROUNDDOWN: 'ABRUNDEN', ROUNDUP: 'AUFRUNDEN', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index e2cd117605..3c6bd085ca 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -105,6 +105,7 @@ const dictionary: RawTranslationPackage = { PROPER: 'PROPER', RADIANS: 'RADIANS', RAND: 'RAND', + REPT: 'REPT', ROUND: 'ROUND', ROUNDDOWN: 'ROUNDDOWN', ROUNDUP: 'ROUNDUP', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index 08b15e2d8b..695ff52a41 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -105,6 +105,7 @@ export const dictionary: RawTranslationPackage = { PROPER: 'NOMPROPIO', RADIANS: 'RADIANES', RAND: 'ALEATORIO', + REPT: 'REPETIR', ROUND: 'REDONDEAR', ROUNDDOWN: 'REDONDEAR.MENOS', ROUNDUP: 'REDONDEAR.MAS', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index e7180e596e..0b2ed05f04 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -105,6 +105,7 @@ const dictionary: RawTranslationPackage = { PROPER: 'ERISNIMI', RADIANS: 'RADIAANIT', RAND: 'SATUNNAISLUKU', + REPT: 'TOISTA', ROUND: 'PYÖRISTÄ', ROUNDDOWN: 'PYÖRISTÄ.DES.ALAS', ROUNDUP: 'PYÖRISTÄ.DES.YLÖS', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index 205d942c34..b965f75fe8 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -105,6 +105,7 @@ const dictionary: RawTranslationPackage = { PROPER: 'NOMPROPRE', RADIANS: 'RADIANS', RAND: 'ALEA', + REPT: 'REPT', ROUND: 'ARRONDI', ROUNDDOWN: 'ARRONDI.INF', ROUNDUP: 'ARRONDI.SUP', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index aa3a19b0ce..de47fd36ee 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -105,6 +105,7 @@ const dictionary: RawTranslationPackage = { PROPER: 'TNÉV', RADIANS: 'RADIÁN', RAND: 'VÉL', + REPT: 'SOKSZOR', ROUND: 'KEREKÍTÉS', ROUNDDOWN: 'KEREK.LE', ROUNDUP: 'KEREK.FEL', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index f9d194c60b..cda0463313 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -105,6 +105,7 @@ const dictionary: RawTranslationPackage = { PROPER: 'MAIUSC.INIZ', RADIANS: 'RADIANTI', RAND: 'CASUALE', + REPT: 'RIPETI', ROUND: 'ARROTONDA', ROUNDDOWN: 'ARROTONDA.PER.DIF', ROUNDUP: 'ARROTONDA.PER.ECC', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index 8537f073b1..217b399863 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -105,6 +105,7 @@ const dictionary: RawTranslationPackage = { PROPER: 'STOR.FORBOKSTAV', RADIANS: 'RADIANER', RAND: 'TILFELDIG', + REPT: 'GJENTA', ROUND: 'AVRUND', ROUNDDOWN: 'AVRUND.NED', ROUNDUP: 'AVRUND.OPP', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index 48a56f5770..d8cb165bef 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -105,6 +105,7 @@ const dictionary: RawTranslationPackage = { PROPER: 'BEGINLETTERS', RADIANS: 'RADIALEN', RAND: 'ASELECT', + REPT: 'HERHALING', ROUND: 'AFRONDEN', ROUNDDOWN: 'AFRONDEN.NAAR.BENEDEN', ROUNDUP: 'AFRONDEN.NAAR.BOVEN', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index f840475780..e2f2f75da1 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -105,6 +105,7 @@ const dictionary: RawTranslationPackage = { PROPER: 'Z.WIELKIEJ.LITERY', RADIANS: 'RADIANY', RAND: 'LOSUJ', + REPT: 'POWT', ROUND: 'ZAOKR', ROUNDDOWN: 'ZAOKR.DÓŁ', ROUNDUP: 'ZAOKR.GÓRA', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index 89f0d57865..a87bc10331 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -105,6 +105,7 @@ const dictionary: RawTranslationPackage = { PROPER: 'PRI.MAIÚSCULA', RADIANS: 'RADIANOS', RAND: 'ALEATÓRIO', + REPT: 'REPT', ROUND: 'ARRED', ROUNDDOWN: 'ARREDONDAR.PARA.BAIXO', ROUNDUP: 'ARREDONDAR.PARA.CIMA', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index 82d5bfc1fe..4b586626db 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -105,6 +105,7 @@ const dictionary: RawTranslationPackage = { PROPER: 'ПРОПНАЧ', RADIANS: 'РАДИАНЫ', RAND: 'СЛЧИС', + REPT: 'ПОВТОР', ROUND: 'ОКРУГЛ', ROUNDDOWN: 'ОКРУГЛВНИЗ', ROUNDUP: 'ОКРУГЛВВЕРХ', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index 9bdd7953ff..d5a98588c6 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -105,6 +105,7 @@ const dictionary: RawTranslationPackage = { PROPER: 'INITIAL', RADIANS: 'RADIANER', RAND: 'SLUMP', + REPT: 'REP', ROUND: 'AVRUNDA', ROUNDDOWN: 'AVRUNDA.NEDÅT', ROUNDUP: 'AVRUNDA.UPPÅT', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index 0f90a98a9c..7113f4218d 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -105,6 +105,7 @@ const dictionary: RawTranslationPackage = { PROPER: 'YAZIM.DÜZENİ', RADIANS: 'RADYAN', RAND: 'S_SAYI_ÜRET', + REPT: 'YİNELE', ROUND: 'YUVARLA', ROUNDDOWN: 'AŞAĞIYUVARLA', ROUNDUP: 'YUKARIYUVARLA', diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index c8f8b00a91..6e4d1d3634 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -25,7 +25,8 @@ export interface FunctionMetadata { } export interface FunctionPluginDefinition { - new (interpreter: Interpreter): FunctionPlugin, + new(interpreter: Interpreter): FunctionPlugin, + implementedFunctions: ImplementedFunctions, } @@ -81,11 +82,11 @@ export abstract class FunctionPlugin { } protected templateWithOneCoercedToNumberArgument(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (arg: number) => InternalScalarValue): InternalScalarValue { - return this.templateWithOneArgumentCoercion(ast, formulaAddress, (arg: InternalScalarValue) => this.coerceScalarToNumberOrError(arg), fn) + return this.coerceArguments(ast, formulaAddress, [this.coerceScalarToNumberOrError], fn) } protected templateWithOneCoercedToStringArgument(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (arg: string) => InternalScalarValue): InternalScalarValue { - return this.templateWithOneArgumentCoercion(ast, formulaAddress, coerceScalarToString, fn) + return this.coerceArguments(ast, formulaAddress, [coerceScalarToString], fn) } protected validateTwoNumericArguments(ast: ProcedureAst, formulaAddress: SimpleCellAddress): [number, number] | CellError { @@ -138,32 +139,36 @@ export abstract class FunctionPlugin { return value } - protected coerceScalarToNumberOrError(arg: InternalScalarValue): number | CellError { - return this.interpreter.arithmeticHelper.coerceScalarToNumberOrError(arg) - } + protected coerceScalarToNumberOrError = (arg: InternalScalarValue): number | CellError => this.interpreter.arithmeticHelper.coerceScalarToNumberOrError(arg) - private templateWithOneArgumentCoercion( + protected coerceArguments = ( ast: ProcedureAst, formulaAddress: SimpleCellAddress, - coerceFunction: (arg: InternalScalarValue) => InternalScalarValue, - fn: (arg: any) => InternalScalarValue, - ) { - if (ast.args.length !== 1) { + coerceFunctions: ((arg: InternalScalarValue) => InternalScalarValue)[], + fn: (...arg: any) => InternalScalarValue, + ) => { + const numberOfArguments = coerceFunctions.length + + if (ast.args.length !== numberOfArguments) { return new CellError(ErrorType.NA) } if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { return new CellError(ErrorType.NUM) } - const arg = this.evaluateAst(ast.args[0], formulaAddress) - if (arg instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - const coercedArg = coerceFunction(arg) - if (coercedArg instanceof CellError) { - return coercedArg - } else { - return fn(coercedArg) + const coercedArguments: InternalScalarValue[] = [] + for (let i = 0; i < ast.args.length; ++i) { + const arg = this.evaluateAst(ast.args[i], formulaAddress) + if (arg instanceof SimpleRangeValue) { + return new CellError(ErrorType.VALUE) + } + const coercedArg = coerceFunctions[i](arg) + if (coercedArg instanceof CellError) { + return coercedArg + } + coercedArguments.push(coercedArg) } + + return fn(...coercedArguments) } } diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index 553eedc6e3..ab7fd4238e 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -30,6 +30,9 @@ export class TextPlugin extends FunctionPlugin { }, 'CLEAN': { method: 'clean' + }, + 'REPT': { + method: 'rept' } } @@ -121,4 +124,16 @@ export class TextPlugin extends FunctionPlugin { return arg.replace(/[\u0000-\u001F]/g, '') }) } + + public rept(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.coerceArguments(ast, formulaAddress, [ + coerceScalarToString, + this.coerceScalarToNumberOrError + ], (text: string, count: number) => { + if (count < 0) { + return new CellError(ErrorType.VALUE) + } + return text.repeat(count) + }) + } } diff --git a/test/interpreter/function-rept.spec.ts b/test/interpreter/function-rept.spec.ts new file mode 100644 index 0000000000..35a508e1c9 --- /dev/null +++ b/test/interpreter/function-rept.spec.ts @@ -0,0 +1,58 @@ +import {ErrorType, HyperFormula} from '../../src' +import {adr, detailedError} from '../testUtils' + +describe('Function REPT', () => { + it('should return N/A when number of arguments is incorrect', () => { + const engine = HyperFormula.buildFromArray([ + ['=REPT()'], + ['=REPT("foo")'], + ['=REPT("foo", 1, 2)'] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NA)) + expect(engine.getCellValue(adr('A2'))).toEqual(detailedError(ErrorType.NA)) + expect(engine.getCellValue(adr('A3'))).toEqual(detailedError(ErrorType.NA)) + }) + + it('should return VALUE when wrong type of second parameter', () => { + const engine = HyperFormula.buildFromArray([ + ['=REPT("foo", "bar")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.VALUE)) + }) + + it('should return VALUE when second parameter is less than 0', () => { + const engine = HyperFormula.buildFromArray([ + ['=REPT("foo", -1)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.VALUE)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=REPT("foo", 0)'], + ['=REPT("foo", 3)'], + ['=REPT(1, 5)'], + ['=REPT("Na", 7)&" Batman!"'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual('') + expect(engine.getCellValue(adr('A2'))).toEqual('foofoofoo') + expect(engine.getCellValue(adr('A3'))).toEqual('11111') + expect(engine.getCellValue(adr('A4'))).toEqual('NaNaNaNaNaNaNa Batman!') + }) + + it('should coerce other types to string', () => { + const engine = HyperFormula.buildFromArray([ + ['=REPT(1, 1)'], + ['=REPT(5+5, 1)'], + ['=REPT(TRUE(), 1)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual('1') + expect(engine.getCellValue(adr('A2'))).toEqual('10') + expect(engine.getCellValue(adr('A3'))).toEqual('TRUE') + }) +}) \ No newline at end of file From 760d49e78c021069d9060ed2440e7f7d1a3f5b90 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Mon, 29 Jun 2020 13:01:43 +0200 Subject: [PATCH 06/97] More general method for coercion with defaults --- src/interpreter/Interpreter.ts | 11 ++++++++-- src/interpreter/plugin/FunctionPlugin.ts | 27 ++++++++++++++++++------ test/interpreter/function-rept.spec.ts | 4 +++- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/interpreter/Interpreter.ts b/src/interpreter/Interpreter.ts index 605d5d8013..d092ec27f9 100644 --- a/src/interpreter/Interpreter.ts +++ b/src/interpreter/Interpreter.ts @@ -5,7 +5,14 @@ import GPU from 'gpu.js' import {AbsoluteCellRange, AbsoluteColumnRange, AbsoluteRowRange} from '../AbsoluteCellRange' -import {CellError, ErrorType, InternalNoErrorCellValue, invalidSimpleCellAddress, SimpleCellAddress} from '../Cell' +import { + CellError, + EmptyValue, + ErrorType, + InternalNoErrorCellValue, + invalidSimpleCellAddress, + SimpleCellAddress +} from '../Cell' import {ColumnSearchStrategy} from '../ColumnSearch/ColumnSearchStrategy' import {Config} from '../Config' import {DateTimeHelper} from '../DateTimeHelper' @@ -52,7 +59,7 @@ export class Interpreter { public evaluateAst(ast: Ast, formulaAddress: SimpleCellAddress): InterpreterValue { switch (ast.type) { case AstNodeType.EMPTY: { - throw new Error('Empty argument should not be evaluated.') + return EmptyValue } case AstNodeType.CELL_REFERENCE: { const address = ast.reference.toSimpleCellAddress(formulaAddress) diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 6e4d1d3634..c4b9eb6024 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -12,6 +12,7 @@ import {Ast, AstNodeType, ProcedureAst} from '../../parser' import {coerceScalarToString} from '../ArithmeticHelper' import {Interpreter} from '../Interpreter' import {InterpreterValue, SimpleRangeValue} from '../InterpreterValue' +import {Maybe} from '../../Maybe' export interface ImplementedFunctions { [formulaId: string]: FunctionMetadata, @@ -147,18 +148,23 @@ export abstract class FunctionPlugin { coerceFunctions: ((arg: InternalScalarValue) => InternalScalarValue)[], fn: (...arg: any) => InternalScalarValue, ) => { - const numberOfArguments = coerceFunctions.length + return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, coerceFunctions, [], fn) + } - if (ast.args.length !== numberOfArguments) { + protected coerceArgumentsWithDefaults = ( + args: Ast[], + formulaAddress: SimpleCellAddress, + coerceFunctions: ((arg: InternalScalarValue) => InternalScalarValue)[], + defaults: Maybe[], + fn: (...arg: any) => InternalScalarValue + ) => { + if (args.length > coerceFunctions.length) { return new CellError(ErrorType.NA) } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } const coercedArguments: InternalScalarValue[] = [] - for (let i = 0; i < ast.args.length; ++i) { - const arg = this.evaluateAst(ast.args[i], formulaAddress) + for (let i = 0; i < coerceFunctions.length; ++i) { + const arg = this.evaluateArgOrDefault(formulaAddress, args[i], defaults[i]) if (arg instanceof SimpleRangeValue) { return new CellError(ErrorType.VALUE) } @@ -171,4 +177,11 @@ export abstract class FunctionPlugin { return fn(...coercedArguments) } + + protected evaluateArgOrDefault = (formulaAddress: SimpleCellAddress, argAst?: Ast, defaultValue?: InternalScalarValue): InterpreterValue => { + if (argAst !== undefined) { + return this.evaluateAst(argAst, formulaAddress) + } + return defaultValue || new CellError(ErrorType.NA) + } } diff --git a/test/interpreter/function-rept.spec.ts b/test/interpreter/function-rept.spec.ts index 35a508e1c9..335710c093 100644 --- a/test/interpreter/function-rept.spec.ts +++ b/test/interpreter/function-rept.spec.ts @@ -35,13 +35,15 @@ describe('Function REPT', () => { ['=REPT("foo", 0)'], ['=REPT("foo", 3)'], ['=REPT(1, 5)'], + ['=REPT(, 5)'], ['=REPT("Na", 7)&" Batman!"'], ]) expect(engine.getCellValue(adr('A1'))).toEqual('') expect(engine.getCellValue(adr('A2'))).toEqual('foofoofoo') expect(engine.getCellValue(adr('A3'))).toEqual('11111') - expect(engine.getCellValue(adr('A4'))).toEqual('NaNaNaNaNaNaNa Batman!') + expect(engine.getCellValue(adr('A4'))).toEqual('') + expect(engine.getCellValue(adr('A5'))).toEqual('NaNaNaNaNaNaNa Batman!') }) it('should coerce other types to string', () => { From 674e02d9a5cb962fc5a80e7f8144440bdf78d0e3 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Mon, 29 Jun 2020 13:27:18 +0200 Subject: [PATCH 07/97] RIGHT function --- src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + src/interpreter/plugin/TextPlugin.ts | 20 +++++++ test/interpreter/function-right.spec.ts | 76 +++++++++++++++++++++++++ 18 files changed, 112 insertions(+) create mode 100644 test/interpreter/function-right.spec.ts diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index 1fbd25774a..cefcffd6c0 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -106,6 +106,7 @@ const dictionary: RawTranslationPackage = { RADIANS: 'RADIANS', RAND: 'NÁHČÍSLO', REPT: 'OPAKOVAT', + RIGHT: 'ZPRAVA', ROUND: 'ZAOKROUHLIT', ROUNDDOWN: 'ROUNDDOWN', ROUNDUP: 'ROUNDUP', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 3d51bc5de0..ee35a65ea4 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -106,6 +106,7 @@ const dictionary: RawTranslationPackage = { RADIANS: 'RADIANER', RAND: 'SLUMP', REPT: 'GENTAG', + RIGHT: 'HØJRE', ROUND: 'AFRUND', ROUNDDOWN: 'RUND.NED', ROUNDUP: 'RUND.OP', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index ff146b71e4..b1d61f68d8 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -106,6 +106,7 @@ const dictionary: RawTranslationPackage = { RADIANS: 'BOGENMASS', RAND: 'ZUFALLSZAHL', REPT: 'WIEDERHOLEN', + RIGHT: 'RECHTS', ROUND: 'RUNDEN', ROUNDDOWN: 'ABRUNDEN', ROUNDUP: 'AUFRUNDEN', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index 3c6bd085ca..c5e67ba544 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -106,6 +106,7 @@ const dictionary: RawTranslationPackage = { RADIANS: 'RADIANS', RAND: 'RAND', REPT: 'REPT', + RIGHT: 'RIGHT', ROUND: 'ROUND', ROUNDDOWN: 'ROUNDDOWN', ROUNDUP: 'ROUNDUP', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index 695ff52a41..5b8ec0965e 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -106,6 +106,7 @@ export const dictionary: RawTranslationPackage = { RADIANS: 'RADIANES', RAND: 'ALEATORIO', REPT: 'REPETIR', + RIGHT: 'DERECHA', ROUND: 'REDONDEAR', ROUNDDOWN: 'REDONDEAR.MENOS', ROUNDUP: 'REDONDEAR.MAS', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 0b2ed05f04..0cb39a9c23 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -106,6 +106,7 @@ const dictionary: RawTranslationPackage = { RADIANS: 'RADIAANIT', RAND: 'SATUNNAISLUKU', REPT: 'TOISTA', + RIGHT: 'OIKEA', ROUND: 'PYÖRISTÄ', ROUNDDOWN: 'PYÖRISTÄ.DES.ALAS', ROUNDUP: 'PYÖRISTÄ.DES.YLÖS', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index b965f75fe8..74a5c3f976 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -106,6 +106,7 @@ const dictionary: RawTranslationPackage = { RADIANS: 'RADIANS', RAND: 'ALEA', REPT: 'REPT', + RIGHT: 'DROITE', ROUND: 'ARRONDI', ROUNDDOWN: 'ARRONDI.INF', ROUNDUP: 'ARRONDI.SUP', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index de47fd36ee..13951f5c32 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -106,6 +106,7 @@ const dictionary: RawTranslationPackage = { RADIANS: 'RADIÁN', RAND: 'VÉL', REPT: 'SOKSZOR', + RIGHT: 'JOBB', ROUND: 'KEREKÍTÉS', ROUNDDOWN: 'KEREK.LE', ROUNDUP: 'KEREK.FEL', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index cda0463313..1bb4ae10f8 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -106,6 +106,7 @@ const dictionary: RawTranslationPackage = { RADIANS: 'RADIANTI', RAND: 'CASUALE', REPT: 'RIPETI', + RIGHT: 'DESTRA', ROUND: 'ARROTONDA', ROUNDDOWN: 'ARROTONDA.PER.DIF', ROUNDUP: 'ARROTONDA.PER.ECC', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index 217b399863..69104d9032 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -106,6 +106,7 @@ const dictionary: RawTranslationPackage = { RADIANS: 'RADIANER', RAND: 'TILFELDIG', REPT: 'GJENTA', + RIGHT: 'HØYRE', ROUND: 'AVRUND', ROUNDDOWN: 'AVRUND.NED', ROUNDUP: 'AVRUND.OPP', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index d8cb165bef..04ea02c56d 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -106,6 +106,7 @@ const dictionary: RawTranslationPackage = { RADIANS: 'RADIALEN', RAND: 'ASELECT', REPT: 'HERHALING', + RIGHT: 'RECHTS', ROUND: 'AFRONDEN', ROUNDDOWN: 'AFRONDEN.NAAR.BENEDEN', ROUNDUP: 'AFRONDEN.NAAR.BOVEN', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index e2f2f75da1..b61d938e34 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -106,6 +106,7 @@ const dictionary: RawTranslationPackage = { RADIANS: 'RADIANY', RAND: 'LOSUJ', REPT: 'POWT', + RIGHT: 'PRAWY', ROUND: 'ZAOKR', ROUNDDOWN: 'ZAOKR.DÓŁ', ROUNDUP: 'ZAOKR.GÓRA', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index a87bc10331..cea818aa63 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -106,6 +106,7 @@ const dictionary: RawTranslationPackage = { RADIANS: 'RADIANOS', RAND: 'ALEATÓRIO', REPT: 'REPT', + RIGHT: 'DIREITA', ROUND: 'ARRED', ROUNDDOWN: 'ARREDONDAR.PARA.BAIXO', ROUNDUP: 'ARREDONDAR.PARA.CIMA', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index 4b586626db..8e28a03e43 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -106,6 +106,7 @@ const dictionary: RawTranslationPackage = { RADIANS: 'РАДИАНЫ', RAND: 'СЛЧИС', REPT: 'ПОВТОР', + RIGHT: 'ПРАВСИМВ', ROUND: 'ОКРУГЛ', ROUNDDOWN: 'ОКРУГЛВНИЗ', ROUNDUP: 'ОКРУГЛВВЕРХ', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index d5a98588c6..f273097ec2 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -106,6 +106,7 @@ const dictionary: RawTranslationPackage = { RADIANS: 'RADIANER', RAND: 'SLUMP', REPT: 'REP', + RIGHT: 'HÖGER', ROUND: 'AVRUNDA', ROUNDDOWN: 'AVRUNDA.NEDÅT', ROUNDUP: 'AVRUNDA.UPPÅT', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index 7113f4218d..3660d56594 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -106,6 +106,7 @@ const dictionary: RawTranslationPackage = { RADIANS: 'RADYAN', RAND: 'S_SAYI_ÜRET', REPT: 'YİNELE', + RIGHT: 'SAĞ', ROUND: 'YUVARLA', ROUNDDOWN: 'AŞAĞIYUVARLA', ROUNDUP: 'YUKARIYUVARLA', diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index ab7fd4238e..06607b52c6 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -33,6 +33,9 @@ export class TextPlugin extends FunctionPlugin { }, 'REPT': { method: 'rept' + }, + 'RIGHT': { + method: 'right' } } @@ -136,4 +139,21 @@ export class TextPlugin extends FunctionPlugin { return text.repeat(count) }) } + + public right(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [ + coerceScalarToString, + this.coerceScalarToNumberOrError + ], [ + undefined, + 1 + ], (text: string, lenght: number) => { + if (lenght < 0) { + return new CellError(ErrorType.VALUE) + } else if (lenght === 0) { + return '' + } + return text.slice(-lenght) + }) + } } diff --git a/test/interpreter/function-right.spec.ts b/test/interpreter/function-right.spec.ts new file mode 100644 index 0000000000..67253d82fd --- /dev/null +++ b/test/interpreter/function-right.spec.ts @@ -0,0 +1,76 @@ +import {ErrorType, HyperFormula} from '../../src' +import {adr, detailedError} from '../testUtils' + +describe('Function RIGHT', () => { + it('should return N/A when number of arguments is incorrect', () => { + const engine = HyperFormula.buildFromArray([ + ['=RIGHT()'], + ['=RIGHT("foo", 1, 2)'] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NA)) + expect(engine.getCellValue(adr('A2'))).toEqual(detailedError(ErrorType.NA)) + }) + + it('should return VALUE when wrong type of second parameter', () => { + const engine = HyperFormula.buildFromArray([ + ['=RIGHT("foo", "bar")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.VALUE)) + }) + + it('should work with empty argument', () => { + const engine = HyperFormula.buildFromArray([ + ['=RIGHT(, 1)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual('') + }) + + it('should return one character by default', () => { + const engine = HyperFormula.buildFromArray([ + ['=RIGHT("bar")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual('r') + }) + + it('should return VALUE when second parameter is less than 0', () => { + const engine = HyperFormula.buildFromArray([ + ['=RIGHT("foo", -1)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.VALUE)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=RIGHT("", 4)'], + ['=RIGHT("bar", 0)'], + ['=RIGHT("bar", 1)'], + ['=RIGHT("bar", 3)'], + ['=RIGHT("bar", 4)'], + ['=RIGHT(123, 2)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual('') + expect(engine.getCellValue(adr('A2'))).toEqual('') + expect(engine.getCellValue(adr('A3'))).toEqual('r') + expect(engine.getCellValue(adr('A4'))).toEqual('bar') + expect(engine.getCellValue(adr('A5'))).toEqual('bar') + expect(engine.getCellValue(adr('A6'))).toEqual('23') + }) + + it('should coerce other types to string', () => { + const engine = HyperFormula.buildFromArray([ + ['=RIGHT(1, 1)'], + ['=RIGHT(5+5, 1)'], + ['=RIGHT(TRUE(), 1)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual('1') + expect(engine.getCellValue(adr('A2'))).toEqual('0') + expect(engine.getCellValue(adr('A3'))).toEqual('E') + }) +}) \ No newline at end of file From 11e0d1b06bae79dc72fcda087f65ade90db34f10 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Mon, 29 Jun 2020 13:38:59 +0200 Subject: [PATCH 08/97] LEFT function --- src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + src/interpreter/plugin/TextPlugin.ts | 18 ++++++ test/interpreter/function-left.spec.ts | 76 ++++++++++++++++++++++++++ 18 files changed, 110 insertions(+) create mode 100644 test/interpreter/function-left.spec.ts diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index cefcffd6c0..b90fbdfd4c 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -81,6 +81,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'JE.ČISLO', ISODD: 'ISODD', ISTEXT: 'JE.TEXT', + LEFT: 'ZLEVA', LEN: 'DÉLKA', LN: 'LN', LOG: 'LOGZ', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index ee35a65ea4..f5dfed8ead 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -81,6 +81,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ER.TAL', ISODD: 'ER.ULIGE', ISTEXT: 'ER.TEKST', + LEFT: 'VENSTRE', LEN: 'LÆNGDE', LN: 'LN', LOG: 'LOG', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index b1d61f68d8..d99cd57cb3 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -81,6 +81,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ISTZAHL', ISODD: 'ISTUNGERADE', ISTEXT: 'ISTTEXT', + LEFT: 'LINKS', LEN: 'LÄNGE', LN: 'LN', LOG: 'LOG', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index c5e67ba544..0c80d537bc 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -81,6 +81,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ISNUMBER', ISODD: 'ISODD', ISTEXT: 'ISTEXT', + LEFT: 'LEFT', LEN: 'LEN', LN: 'LN', LOG: 'LOG', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index 5b8ec0965e..f15dc3a1e6 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -81,6 +81,7 @@ export const dictionary: RawTranslationPackage = { ISNUMBER: 'ESNUMERO', ISODD: 'ES.IMPAR', ISTEXT: 'ESTEXTO', + LEFT: 'IZQUIERDA', LEN: 'LARGO', LN: 'LN', LOG: 'LOG', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 0cb39a9c23..870229ebe6 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -81,6 +81,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ONLUKU', ISODD: 'ONPARITON', ISTEXT: 'ONTEKSTI', + LEFT: 'VASEN', LEN: 'PITUUS', LN: 'LUONNLOG', LOG: 'LOG', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index 74a5c3f976..f65557e518 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -81,6 +81,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ESTNUM', ISODD: 'EST.IMPAIR', ISTEXT: 'ESTTEXTE', + LEFT: 'GAUCHE', LEN: 'NBCAR', LN: 'LN', LOG: 'LOG', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index 13951f5c32..dd75f35f26 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -81,6 +81,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'SZÁM', ISODD: 'PÁRATLANE', ISTEXT: 'SZÖVEG.E', + LEFT: 'BAL', LEN: 'HOSSZ', LN: 'LN', LOG: 'LOG', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index 1bb4ae10f8..f5068823bf 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -81,6 +81,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'VAL.NUMERO', ISODD: 'VAL.DISPARI', ISTEXT: 'VAL.TESTO', + LEFT: 'SINISTRA', LEN: 'LUNGHEZZA', LN: 'LN', LOG: 'LOG', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index 69104d9032..f37c165148 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -81,6 +81,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ERTALL', ISODD: 'ERODDE', ISTEXT: 'ERTEKST', + LEFT: 'VENSTRE', LEN: 'LENGDE', LN: 'LN', LOG: 'LOG', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index 04ea02c56d..b1f388f7e3 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -81,6 +81,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ISGETAL', ISODD: 'IS.ONEVEN', ISTEXT: 'ISTEKST', + LEFT: 'LINKS', LEN: 'PITUUS', LN: 'LN', LOG: 'LOG', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index b61d938e34..17f32645ac 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -81,6 +81,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'CZY.LICZBA', ISODD: 'CZY.NIEPARZYSTE', ISTEXT: 'CZY.TEKST', + LEFT: 'LEWY', LEN: 'DŁ', LN: 'LN', LOG: 'LOG', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index cea818aa63..8ca8e9ebef 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -81,6 +81,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ÉNÚM', ISODD: 'ÉIMPAR', ISTEXT: 'ÉTEXTO', + LEFT: 'ESQUERDA', LEN: 'NÚM.CARACT', LN: 'LN', LOG: 'LOG', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index 8e28a03e43..a5e30198a0 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -81,6 +81,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ЕЧИСЛО', ISODD: 'ЕНЕЧЁТ', ISTEXT: 'ЕТЕКСТ', + LEFT: 'ЛЕВСИМВ', LEN: 'ДЛСТР', LN: 'LN', LOG: 'LOG', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index f273097ec2..e5b9d4c2df 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -81,6 +81,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ÄRTAL', ISODD: 'ÄRUDDA', ISTEXT: 'ÄRTEXT', + LEFT: 'VÄNSTER', LEN: 'LÄNGD', LN: 'LN', LOG: 'LOG', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index 3660d56594..5a20d2bc77 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -81,6 +81,7 @@ const dictionary: RawTranslationPackage = { ISNUMBER: 'ESAYIYSA', ISODD: 'TEKMİ', ISTEXT: 'EMETİNSE', + LEFT: 'SOL', LEN: 'UZUNLUK', LN: 'LN', LOG: 'LOG', diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index 06607b52c6..792d815bb2 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -36,6 +36,9 @@ export class TextPlugin extends FunctionPlugin { }, 'RIGHT': { method: 'right' + }, + 'LEFT': { + method: 'left' } } @@ -156,4 +159,19 @@ export class TextPlugin extends FunctionPlugin { return text.slice(-lenght) }) } + + public left(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [ + coerceScalarToString, + this.coerceScalarToNumberOrError + ], [ + undefined, + 1 + ], (text: string, lenght: number) => { + if (lenght < 0) { + return new CellError(ErrorType.VALUE) + } + return text.slice(0, lenght) + }) + } } diff --git a/test/interpreter/function-left.spec.ts b/test/interpreter/function-left.spec.ts new file mode 100644 index 0000000000..4b6ca364a9 --- /dev/null +++ b/test/interpreter/function-left.spec.ts @@ -0,0 +1,76 @@ +import {ErrorType, HyperFormula} from '../../src' +import {adr, detailedError} from '../testUtils' + +describe('Function LEFT', () => { + it('should return N/A when number of arguments is incorrect', () => { + const engine = HyperFormula.buildFromArray([ + ['=LEFT()'], + ['=LEFT("foo", 1, 2)'] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NA)) + expect(engine.getCellValue(adr('A2'))).toEqual(detailedError(ErrorType.NA)) + }) + + it('should return VALUE when wrong type of second parameter', () => { + const engine = HyperFormula.buildFromArray([ + ['=LEFT("foo", "bar")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.VALUE)) + }) + + it('should work with empty argument', () => { + const engine = HyperFormula.buildFromArray([ + ['=LEFT(, 1)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual('') + }) + + it('should return one character by default', () => { + const engine = HyperFormula.buildFromArray([ + ['=LEFT("bar")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual('b') + }) + + it('should return VALUE when second parameter is less than 0', () => { + const engine = HyperFormula.buildFromArray([ + ['=LEFT("foo", -1)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.VALUE)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=LEFT("", 4)'], + ['=LEFT("bar", 0)'], + ['=LEFT("bar", 1)'], + ['=LEFT("bar", 3)'], + ['=LEFT("bar", 4)'], + ['=LEFT(123, 2)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual('') + expect(engine.getCellValue(adr('A2'))).toEqual('') + expect(engine.getCellValue(adr('A3'))).toEqual('b') + expect(engine.getCellValue(adr('A4'))).toEqual('bar') + expect(engine.getCellValue(adr('A5'))).toEqual('bar') + expect(engine.getCellValue(adr('A6'))).toEqual('12') + }) + + it('should coerce other types to string', () => { + const engine = HyperFormula.buildFromArray([ + ['=LEFT(1, 1)'], + ['=LEFT(5+5, 1)'], + ['=LEFT(TRUE(), 1)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual('1') + expect(engine.getCellValue(adr('A2'))).toEqual('1') + expect(engine.getCellValue(adr('A3'))).toEqual('T') + }) +}) \ No newline at end of file From 292dac4111ae4f984c9fad5283e782f94078bd47 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Tue, 30 Jun 2020 19:06:15 +0200 Subject: [PATCH 09/97] Fix typo --- src/interpreter/plugin/TextPlugin.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index 792d815bb2..043d022b04 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -150,13 +150,13 @@ export class TextPlugin extends FunctionPlugin { ], [ undefined, 1 - ], (text: string, lenght: number) => { - if (lenght < 0) { + ], (text: string, length: number) => { + if (length < 0) { return new CellError(ErrorType.VALUE) - } else if (lenght === 0) { + } else if (length === 0) { return '' } - return text.slice(-lenght) + return text.slice(-length) }) } @@ -167,11 +167,11 @@ export class TextPlugin extends FunctionPlugin { ], [ undefined, 1 - ], (text: string, lenght: number) => { - if (lenght < 0) { + ], (text: string, length: number) => { + if (length < 0) { return new CellError(ErrorType.VALUE) } - return text.slice(0, lenght) + return text.slice(0, length) }) } } From e616628600ba1e983ffd9706448029dfa5737725 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Tue, 30 Jun 2020 19:58:16 +0200 Subject: [PATCH 10/97] SEARCH function --- src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + src/interpreter/ArithmeticHelper.ts | 10 ++- src/interpreter/plugin/TextPlugin.ts | 31 +++++++ test/interpreter/function-search.spec.ts | 108 +++++++++++++++++++++++ 19 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 test/interpreter/function-search.spec.ts diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index b90fbdfd4c..807a1a3540 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -112,6 +112,7 @@ const dictionary: RawTranslationPackage = { ROUNDDOWN: 'ROUNDDOWN', ROUNDUP: 'ROUNDUP', ROWS: 'ŘÁDKY', + SEARCH: 'HLEDAT', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'ODMOCNINA', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index f5dfed8ead..5ea8818c3b 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -112,6 +112,7 @@ const dictionary: RawTranslationPackage = { ROUNDDOWN: 'RUND.NED', ROUNDUP: 'RUND.OP', ROWS: 'RÆKKER', + SEARCH: 'SØG', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'KVROD', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index d99cd57cb3..756bea0360 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -112,6 +112,7 @@ const dictionary: RawTranslationPackage = { ROUNDDOWN: 'ABRUNDEN', ROUNDUP: 'AUFRUNDEN', ROWS: 'ZEILEN', + SEARCH: 'SUCHEN', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'WURZEL', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index 0c80d537bc..ea7cc88ea0 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -112,6 +112,7 @@ const dictionary: RawTranslationPackage = { ROUNDDOWN: 'ROUNDDOWN', ROUNDUP: 'ROUNDUP', ROWS: 'ROWS', + SEARCH: 'SEARCH', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'SQRT', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index f15dc3a1e6..d8b14d0408 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -112,6 +112,7 @@ export const dictionary: RawTranslationPackage = { ROUNDDOWN: 'REDONDEAR.MENOS', ROUNDUP: 'REDONDEAR.MAS', ROWS: 'FILAS', + SEARCH: 'HALLAR', SIN: 'SENO', SPLIT: 'SPLIT', SQRT: 'RAIZ', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 870229ebe6..2a2ae2bf53 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -112,6 +112,7 @@ const dictionary: RawTranslationPackage = { ROUNDDOWN: 'PYÖRISTÄ.DES.ALAS', ROUNDUP: 'PYÖRISTÄ.DES.YLÖS', ROWS: 'RIVIT', + SEARCH: 'KÄY.LÄPI', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'NELIÖJUURI', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index f65557e518..2f29e2e68d 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -112,6 +112,7 @@ const dictionary: RawTranslationPackage = { ROUNDDOWN: 'ARRONDI.INF', ROUNDUP: 'ARRONDI.SUP', ROWS: 'LIGNES', + SEARCH: 'CHERCHE', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'RACINE', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index dd75f35f26..268e5a1d5e 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -112,6 +112,7 @@ const dictionary: RawTranslationPackage = { ROUNDDOWN: 'KEREK.LE', ROUNDUP: 'KEREK.FEL', ROWS: 'SOROK', + SEARCH: 'SZÖVEG.KERES', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'GYÖK', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index f5068823bf..95e3b67c89 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -112,6 +112,7 @@ const dictionary: RawTranslationPackage = { ROUNDDOWN: 'ARROTONDA.PER.DIF', ROUNDUP: 'ARROTONDA.PER.ECC', ROWS: 'RIGHE', + SEARCH: 'RICERCA', SIN: 'SEN', SPLIT: 'SPLIT', SQRT: 'RADQ', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index f37c165148..ab5439ca51 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -112,6 +112,7 @@ const dictionary: RawTranslationPackage = { ROUNDDOWN: 'AVRUND.NED', ROUNDUP: 'AVRUND.OPP', ROWS: 'RADER', + SEARCH: 'SØK', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'ROT', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index b1f388f7e3..c9b24b18a3 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -112,6 +112,7 @@ const dictionary: RawTranslationPackage = { ROUNDDOWN: 'AFRONDEN.NAAR.BENEDEN', ROUNDUP: 'AFRONDEN.NAAR.BOVEN', ROWS: 'RIJEN', + SEARCH: 'VIND.SPEC', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'WORTEL', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index 17f32645ac..d576abf02f 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -112,6 +112,7 @@ const dictionary: RawTranslationPackage = { ROUNDDOWN: 'ZAOKR.DÓŁ', ROUNDUP: 'ZAOKR.GÓRA', ROWS: 'ILE.WIERSZY', + SEARCH: 'SZUKAJ.TEKST', SIN: 'SIN', SPLIT: 'PODZIEL.TEKST', SQRT: 'PIERWIASTEK', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index 8ca8e9ebef..865650ec34 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -112,6 +112,7 @@ const dictionary: RawTranslationPackage = { ROUNDDOWN: 'ARREDONDAR.PARA.BAIXO', ROUNDUP: 'ARREDONDAR.PARA.CIMA', ROWS: 'LINS', + SEARCH: 'LOCALIZAR', SIN: 'SEN', SPLIT: 'SPLIT', SQRT: 'RAIZ', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index a5e30198a0..f59c51a889 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -112,6 +112,7 @@ const dictionary: RawTranslationPackage = { ROUNDDOWN: 'ОКРУГЛВНИЗ', ROUNDUP: 'ОКРУГЛВВЕРХ', ROWS: 'ЧСТРОК', + SEARCH: 'ПОИСК', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'КОРЕНЬ', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index e5b9d4c2df..b6a7b3d2e1 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -112,6 +112,7 @@ const dictionary: RawTranslationPackage = { ROUNDDOWN: 'AVRUNDA.NEDÅT', ROUNDUP: 'AVRUNDA.UPPÅT', ROWS: 'RADER', + SEARCH: 'SÖK', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'ROT', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index 5a20d2bc77..8dda35c289 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -112,6 +112,7 @@ const dictionary: RawTranslationPackage = { ROUNDDOWN: 'AŞAĞIYUVARLA', ROUNDUP: 'YUKARIYUVARLA', ROWS: 'SATIRSAY', + SEARCH: 'MBUL', SIN: 'SİN', SPLIT: 'SPLIT', SQRT: 'KAREKÖK', diff --git a/src/interpreter/ArithmeticHelper.ts b/src/interpreter/ArithmeticHelper.ts index 04000f6341..7e6908ad6c 100644 --- a/src/interpreter/ArithmeticHelper.ts +++ b/src/interpreter/ArithmeticHelper.ts @@ -44,6 +44,12 @@ export class ArithmeticHelper { } } + public searchString(pattern: string, text: string): number { + const regexp = this.buildRegex(pattern, false) + const result = regexp.exec(text) + return result?.index ?? -1 + } + public requiresRegex(pattern: string): boolean { if(!this.config.useRegularExpresssions && !this.config.useWildcards) { return !this.config.matchWholeCell @@ -57,7 +63,7 @@ export class ArithmeticHelper { return false } - private buildRegex(pattern: string): RegExp { + private buildRegex(pattern: string, matchWholeCell: boolean = true): RegExp { pattern = this.normalizeString(pattern) let regexpStr let useWildcards = this.config.useWildcards @@ -77,7 +83,7 @@ export class ArithmeticHelper { } else { regexpStr = escapeAllCharacters(pattern, this.config.caseSensitive) } - if(this.config.matchWholeCell) { + if(this.config.matchWholeCell && matchWholeCell) { return RegExp('^('+ regexpStr + ')$') } else { return RegExp(regexpStr) diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index 043d022b04..fe28d638ea 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -39,6 +39,9 @@ export class TextPlugin extends FunctionPlugin { }, 'LEFT': { method: 'left' + }, + 'SEARCH': { + method: 'search' } } @@ -174,4 +177,32 @@ export class TextPlugin extends FunctionPlugin { return text.slice(0, length) }) } + + public search(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [ + coerceScalarToString, + coerceScalarToString, + this.coerceScalarToNumberOrError + ], [ + undefined, + undefined, + 1 + ], (pattern, text: string, startIndex: number) => { + if (startIndex < 1 || startIndex > text.length) { + return new CellError(ErrorType.VALUE) + } + + const normalizedText = text.substr(startIndex - 1).toLowerCase() + + let index: number + if (this.interpreter.arithmeticHelper.requiresRegex(pattern)) { + index = this.interpreter.arithmeticHelper.searchString(pattern, normalizedText) + } else { + index = normalizedText.indexOf(pattern.toLowerCase()) + } + + index = index + startIndex + return index > 0 ? index : new CellError(ErrorType.VALUE) + }) + } } diff --git a/test/interpreter/function-search.spec.ts b/test/interpreter/function-search.spec.ts new file mode 100644 index 0000000000..31cfdd52c5 --- /dev/null +++ b/test/interpreter/function-search.spec.ts @@ -0,0 +1,108 @@ +import {ErrorType, HyperFormula} from '../../src' +import {adr, detailedError} from '../testUtils' + +describe('Function SEARCH', () => { + it('should return N/A when number of arguments is incorrect', () => { + const engine = HyperFormula.buildFromArray([ + ['=SEARCH()'], + ['=SEARCH("foo")'], + ['=SEARCH("foo", 1, 2, 3)'] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NA)) + expect(engine.getCellValue(adr('A2'))).toEqual(detailedError(ErrorType.NA)) + expect(engine.getCellValue(adr('A3'))).toEqual(detailedError(ErrorType.NA)) + }) + + it('should return VALUE when wrong type of third parameter', () => { + const engine = HyperFormula.buildFromArray([ + ['=SEARCH("foo", "bar", "baz")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.VALUE)) + }) + + it('should return VALUE if third parameter is not between 1 and text length', () => { + const engine = HyperFormula.buildFromArray([ + ['=SEARCH("foo", "bar", 0)'], + ['=SEARCH("foo", "bar", -1)'], + ['=SEARCH("foo", "bar", 4)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.VALUE)) + expect(engine.getCellValue(adr('A2'))).toEqual(detailedError(ErrorType.VALUE)) + expect(engine.getCellValue(adr('A3'))).toEqual(detailedError(ErrorType.VALUE)) + }) + + it('should work with simple strings', () => { + const engine = HyperFormula.buildFromArray([ + ['=SEARCH("f", "foo")'], + ['=SEARCH("o", "foo")'], + ['=SEARCH("o", "foo", 3)'], + ['=SEARCH("g", "foo")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(1) + expect(engine.getCellValue(adr('A2'))).toEqual(2) + expect(engine.getCellValue(adr('A3'))).toEqual(3) + expect(engine.getCellValue(adr('A4'))).toEqual(detailedError(ErrorType.VALUE)) + }) + + it('should work with wildcards', () => { + const engine = HyperFormula.buildFromArray([ + ['=SEARCH("*f", "foobarbaz")'], + ['=SEARCH("b*b", "foobarbaz")'], + ['=SEARCH("b?z", "foobarbaz")'], + ['=SEARCH("b?b", "foobarbaz")'], + ['=SEARCH("?b", "foobarbaz", 5)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(1) + expect(engine.getCellValue(adr('A2'))).toEqual(4) + expect(engine.getCellValue(adr('A3'))).toEqual(7) + expect(engine.getCellValue(adr('A4'))).toEqual(detailedError(ErrorType.VALUE)) + expect(engine.getCellValue(adr('A5'))).toEqual(6) + }) + + it('should work with regular expressions', () => { + const engine = HyperFormula.buildFromArray([ + ['=SEARCH(".*f", "foobarbaz")'], + ['=SEARCH("b.*b", "foobarbaz")'], + ['=SEARCH("b.z", "foobarbaz")'], + ['=SEARCH("b.b", "foobarbaz")'], + ['=SEARCH(".b", "foobarbaz", 5)'], + ], { useRegularExpresssions: true }) + + expect(engine.getCellValue(adr('A1'))).toEqual(1) + expect(engine.getCellValue(adr('A2'))).toEqual(4) + expect(engine.getCellValue(adr('A3'))).toEqual(7) + expect(engine.getCellValue(adr('A4'))).toEqual(detailedError(ErrorType.VALUE)) + expect(engine.getCellValue(adr('A5'))).toEqual(6) + }) + + it('should be case insensitive', () => { + const engine = HyperFormula.buildFromArray([ + ['=SEARCH("R", "bar")'], + ['=SEARCH("r", "baR")'], + ['=SEARCH("?R", "bar")'], + ['=SEARCH("*r", "baR")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(3) + expect(engine.getCellValue(adr('A2'))).toEqual(3) + expect(engine.getCellValue(adr('A3'))).toEqual(2) + expect(engine.getCellValue(adr('A4'))).toEqual(1) + }) + + it('should coerce other types to string', () => { + const engine = HyperFormula.buildFromArray([ + ['=SEARCH(1, 1, 1)'], + ['=SEARCH(0, 5+5)'], + ['=SEARCH("U", TRUE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(1) + expect(engine.getCellValue(adr('A2'))).toEqual(2) + expect(engine.getCellValue(adr('A3'))).toEqual(3) + }) +}) \ No newline at end of file From 06a83a91fe3e38a663c906292a7e4c6891711935 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Tue, 30 Jun 2020 20:03:24 +0200 Subject: [PATCH 11/97] FIND function --- src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + src/interpreter/plugin/TextPlugin.ts | 24 ++++++++ test/interpreter/function-find.spec.ts | 80 ++++++++++++++++++++++++++ 18 files changed, 120 insertions(+) create mode 100644 test/interpreter/function-find.spec.ts diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index 807a1a3540..5a9ea44498 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -68,6 +68,7 @@ const dictionary: RawTranslationPackage = { EVEN: 'ZAOKROUHLIT.NA.SUDÉ', EXP: 'EXP', FALSE: 'NEPRAVDA', + FIND: 'NAJÍT', IF: 'KDYŽ', IFERROR: 'IFERROR', IFNA: 'IFNA', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 5ea8818c3b..42533a25ca 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -68,6 +68,7 @@ const dictionary: RawTranslationPackage = { EVEN: 'LIGE', EXP: 'EKSP', FALSE: 'FALSE', + FIND: 'FIND', IF: 'HVIS', IFERROR: 'HVIS.FEJL', IFNA: 'HVISIT', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index 756bea0360..6f3228547b 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -68,6 +68,7 @@ const dictionary: RawTranslationPackage = { EVEN: 'GERADE', EXP: 'EXP', FALSE: 'FALSCH', + FIND: 'FINDEN', IF: 'WENN', IFERROR: 'WENNFEHLER', IFNA: 'WENNNV', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index ea7cc88ea0..6b259115c3 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -68,6 +68,7 @@ const dictionary: RawTranslationPackage = { EVEN: 'EVEN', EXP: 'EXP', FALSE: 'FALSE', + FIND: 'FIND', IF: 'IF', IFERROR: 'IFERROR', IFNA: 'IFNA', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index d8b14d0408..85c9b3fc88 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -68,6 +68,7 @@ export const dictionary: RawTranslationPackage = { EVEN: 'REDONDEA.PAR', EXP: 'EXP', FALSE: 'FALSO', + FIND: 'ENCONTRAR', IF: 'SI', IFERROR: 'SI.ERROR', IFNA: 'IFNA', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 2a2ae2bf53..179f52870a 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -68,6 +68,7 @@ const dictionary: RawTranslationPackage = { EVEN: 'PARILLINEN', EXP: 'EKSPONENTTI', FALSE: 'EPÄTOSI', + FIND: 'ETSI', IF: 'JOS', IFERROR: 'JOSVIRHE', IFNA: 'JOSPUUTTUU', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index 2f29e2e68d..0c0cdd76f1 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -68,6 +68,7 @@ const dictionary: RawTranslationPackage = { EVEN: 'PAIR', EXP: 'EXP', FALSE: 'FAUX', + FIND: 'TROUVE', IF: 'SI', IFERROR: 'SIERREUR', IFNA: 'SI.NON.DISP', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index 268e5a1d5e..e6e256eec8 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -68,6 +68,7 @@ const dictionary: RawTranslationPackage = { EVEN: 'PÁROS', EXP: 'KITEVŐ', FALSE: 'HAMIS', + FIND: 'SZÖVEG.TALÁL', IF: 'HA', IFERROR: 'HAHIBA', IFNA: 'HAHIÁNYZIK', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index 95e3b67c89..ab30e4a621 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -68,6 +68,7 @@ const dictionary: RawTranslationPackage = { EVEN: 'PARI', EXP: 'EXP', FALSE: 'FALSO', + FIND: 'TROVA', IF: 'SE', IFERROR: 'SE.ERRORE', IFNA: 'SE.NON.DISP.', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index ab5439ca51..dad06fd788 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -68,6 +68,7 @@ const dictionary: RawTranslationPackage = { EVEN: 'AVRUND.TIL.PARTALL', EXP: 'EKSP', FALSE: 'USANN', + FIND: 'FINN', IF: 'HVIS', IFERROR: 'HVISFEIL', IFNA: 'HVIS.IT', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index c9b24b18a3..e7539646d1 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -68,6 +68,7 @@ const dictionary: RawTranslationPackage = { EVEN: 'EVEN', EXP: 'EXP', FALSE: 'ONWAAR', + FIND: 'VIND.ALLES', IF: 'ALS', IFERROR: 'ALS.FOUT', IFNA: 'ALS.NB', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index d576abf02f..458650fdff 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -68,6 +68,7 @@ const dictionary: RawTranslationPackage = { EVEN: 'ZAOKR.DO.PARZ', EXP: 'EXP', FALSE: 'FAŁSZ', + FIND: 'ZNAJDŹ', IF: 'JEŻELI', IFERROR: 'JEŻELI.BŁĄD', IFNA: 'JEŻELI.ND', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index 865650ec34..66ec685dcf 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -68,6 +68,7 @@ const dictionary: RawTranslationPackage = { EVEN: 'PAR', EXP: 'EXP', FALSE: 'FALSO', + FIND: 'PROCURAR', IF: 'SE', IFERROR: 'SEERRO', IFNA: 'SENA', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index f59c51a889..f62b9058bb 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -68,6 +68,7 @@ const dictionary: RawTranslationPackage = { EVEN: 'ЧЁТН', EXP: 'EXP', FALSE: 'ЛОЖЬ', + FIND: 'НАЙТИ', IF: 'ЕСЛИ', IFERROR: 'ЕСЛИОШИБКА', IFNA: 'ЕСНД', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index b6a7b3d2e1..52865fcd76 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -68,6 +68,7 @@ const dictionary: RawTranslationPackage = { EVEN: 'JÄMN', EXP: 'EXP', FALSE: 'FALSKT', + FIND: 'HITTA', IF: 'OM', IFERROR: 'OMFEL', IFNA: 'IFNA', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index 8dda35c289..7fa5ad112e 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -68,6 +68,7 @@ const dictionary: RawTranslationPackage = { EVEN: 'ÇİFT', EXP: 'ÜS', FALSE: 'YANLIŞ', + FIND: 'BUL', IF: 'EĞER', IFERROR: 'EĞERHATA', IFNA: 'EĞERYOKSA', diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index fe28d638ea..4e6d728f02 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -42,6 +42,9 @@ export class TextPlugin extends FunctionPlugin { }, 'SEARCH': { method: 'search' + }, + 'FIND': { + method: 'find' } } @@ -205,4 +208,25 @@ export class TextPlugin extends FunctionPlugin { return index > 0 ? index : new CellError(ErrorType.VALUE) }) } + + public find(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [ + coerceScalarToString, + coerceScalarToString, + this.coerceScalarToNumberOrError + ], [ + undefined, + undefined, + 1 + ], (pattern, text: string, startIndex: number) => { + if (startIndex < 1 || startIndex > text.length) { + return new CellError(ErrorType.VALUE) + } + + const shiftedText = text.substr(startIndex - 1) + const index = shiftedText.indexOf(pattern) + startIndex + + return index > 0 ? index : new CellError(ErrorType.VALUE) + }) + } } diff --git a/test/interpreter/function-find.spec.ts b/test/interpreter/function-find.spec.ts new file mode 100644 index 0000000000..8dbf6e4c90 --- /dev/null +++ b/test/interpreter/function-find.spec.ts @@ -0,0 +1,80 @@ +import {ErrorType, HyperFormula} from '../../src' +import {adr, detailedError} from '../testUtils' + +describe('Function FIND', () => { + it('should return N/A when number of arguments is incorrect', () => { + const engine = HyperFormula.buildFromArray([ + ['=FIND()'], + ['=FIND("foo")'], + ['=FIND("foo", 1, 2, 3)'] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NA)) + expect(engine.getCellValue(adr('A2'))).toEqual(detailedError(ErrorType.NA)) + expect(engine.getCellValue(adr('A3'))).toEqual(detailedError(ErrorType.NA)) + }) + + it('should return VALUE when wrong type of third parameter', () => { + const engine = HyperFormula.buildFromArray([ + ['=FIND("foo", "bar", "baz")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.VALUE)) + }) + + it('should return VALUE if third parameter is not between 1 and text length', () => { + const engine = HyperFormula.buildFromArray([ + ['=FIND("foo", "bar", 0)'], + ['=FIND("foo", "bar", -1)'], + ['=FIND("foo", "bar", 4)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.VALUE)) + expect(engine.getCellValue(adr('A2'))).toEqual(detailedError(ErrorType.VALUE)) + expect(engine.getCellValue(adr('A3'))).toEqual(detailedError(ErrorType.VALUE)) + }) + + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=FIND("f", "foo")'], + ['=FIND("o", "foo")'], + ['=FIND("o", "foo", 3)'], + ['=FIND("g", "foo")'], + ['=FIND("?o", "?o")'], + ['=FIND("?o", "oo")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(1) + expect(engine.getCellValue(adr('A2'))).toEqual(2) + expect(engine.getCellValue(adr('A3'))).toEqual(3) + expect(engine.getCellValue(adr('A4'))).toEqual(detailedError(ErrorType.VALUE)) + expect(engine.getCellValue(adr('A5'))).toEqual(1) + expect(engine.getCellValue(adr('A6'))).toEqual(detailedError(ErrorType.VALUE)) + }) + + it('should be case sensitive', () => { + const engine = HyperFormula.buildFromArray([ + ['=FIND("R", "bar")'], + ['=FIND("r", "bar")'], + ['=FIND("r", "baR")'], + ['=FIND("R", "baR")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.VALUE)) + expect(engine.getCellValue(adr('A2'))).toEqual(3) + expect(engine.getCellValue(adr('A3'))).toEqual(detailedError(ErrorType.VALUE)) + expect(engine.getCellValue(adr('A4'))).toEqual(3) + }) + + it('should coerce other types to string', () => { + const engine = HyperFormula.buildFromArray([ + ['=FIND(1, 1, 1)'], + ['=FIND(0, 5+5)'], + ['=FIND("U", TRUE())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(1) + expect(engine.getCellValue(adr('A2'))).toEqual(2) + expect(engine.getCellValue(adr('A3'))).toEqual(3) + }) +}) \ No newline at end of file From 01b284660028be8f0c368f4a9ad23974b6acbea2 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Tue, 30 Jun 2020 20:17:21 +0200 Subject: [PATCH 12/97] Introduce FunctionArgumentDefinition --- src/index.ts | 3 +- src/interpreter/index.ts | 9 +++++- src/interpreter/plugin/FunctionPlugin.ts | 29 +++++++---------- src/interpreter/plugin/TextPlugin.ts | 40 ++++++++---------------- 4 files changed, 35 insertions(+), 46 deletions(-) diff --git a/src/index.ts b/src/index.ts index 7b3de7c062..abf206947b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -50,7 +50,7 @@ import { UnableToParseError } from './errors' import * as plugins from './interpreter/plugin' -import {FunctionPluginDefinition, FunctionPlugin} from './interpreter' +import {FunctionArgumentDefinition, FunctionPlugin, FunctionPluginDefinition} from './interpreter' import {ColumnRowIndex} from './CrudOperations' /** @internal */ @@ -121,6 +121,7 @@ export { ColumnRowIndex, RawTranslationPackage, FunctionPluginDefinition, + FunctionArgumentDefinition, NamedExpression, NamedExpressionOptions, HyperFormula, diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts index bc6b7a182b..bfd9c517a9 100644 --- a/src/interpreter/index.ts +++ b/src/interpreter/index.ts @@ -3,11 +3,18 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {FunctionMetadata, FunctionPluginDefinition, ImplementedFunctions, FunctionPlugin} from './plugin/FunctionPlugin' +import { + FunctionArgumentDefinition, + FunctionMetadata, + FunctionPlugin, + FunctionPluginDefinition, + ImplementedFunctions +} from './plugin/FunctionPlugin' import {FunctionTranslationsPackage} from './FunctionRegistry' export { FunctionPluginDefinition, + FunctionArgumentDefinition, FunctionPlugin, ImplementedFunctions, FunctionMetadata, diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index c4b9eb6024..ddc80c3f3a 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -31,6 +31,11 @@ export interface FunctionPluginDefinition { implementedFunctions: ImplementedFunctions, } +export interface FunctionArgumentDefinition { + typeCoercionFunction: (arg: InternalScalarValue) => InternalScalarValue, + defaultValue?: InternalScalarValue, +} + export type PluginFunctionType = (ast: ProcedureAst, formulaAddress: SimpleCellAddress) => InternalScalarValue /** @@ -83,11 +88,11 @@ export abstract class FunctionPlugin { } protected templateWithOneCoercedToNumberArgument(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (arg: number) => InternalScalarValue): InternalScalarValue { - return this.coerceArguments(ast, formulaAddress, [this.coerceScalarToNumberOrError], fn) + return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [{ typeCoercionFunction: this.coerceScalarToNumberOrError }], fn) } protected templateWithOneCoercedToStringArgument(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (arg: string) => InternalScalarValue): InternalScalarValue { - return this.coerceArguments(ast, formulaAddress, [coerceScalarToString], fn) + return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [{ typeCoercionFunction: coerceScalarToString }], fn) } protected validateTwoNumericArguments(ast: ProcedureAst, formulaAddress: SimpleCellAddress): [number, number] | CellError { @@ -142,33 +147,23 @@ export abstract class FunctionPlugin { protected coerceScalarToNumberOrError = (arg: InternalScalarValue): number | CellError => this.interpreter.arithmeticHelper.coerceScalarToNumberOrError(arg) - protected coerceArguments = ( - ast: ProcedureAst, - formulaAddress: SimpleCellAddress, - coerceFunctions: ((arg: InternalScalarValue) => InternalScalarValue)[], - fn: (...arg: any) => InternalScalarValue, - ) => { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, coerceFunctions, [], fn) - } - protected coerceArgumentsWithDefaults = ( args: Ast[], formulaAddress: SimpleCellAddress, - coerceFunctions: ((arg: InternalScalarValue) => InternalScalarValue)[], - defaults: Maybe[], + argumentDefinitions: FunctionArgumentDefinition[], fn: (...arg: any) => InternalScalarValue ) => { - if (args.length > coerceFunctions.length) { + if (args.length > argumentDefinitions.length) { return new CellError(ErrorType.NA) } const coercedArguments: InternalScalarValue[] = [] - for (let i = 0; i < coerceFunctions.length; ++i) { - const arg = this.evaluateArgOrDefault(formulaAddress, args[i], defaults[i]) + for (let i = 0; i < argumentDefinitions.length; ++i) { + const arg = this.evaluateArgOrDefault(formulaAddress, args[i], argumentDefinitions[i].defaultValue) if (arg instanceof SimpleRangeValue) { return new CellError(ErrorType.VALUE) } - const coercedArg = coerceFunctions[i](arg) + const coercedArg = argumentDefinitions[i].typeCoercionFunction(arg) if (coercedArg instanceof CellError) { return coercedArg } diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index 4e6d728f02..d91606df5b 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -138,9 +138,9 @@ export class TextPlugin extends FunctionPlugin { } public rept(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.coerceArguments(ast, formulaAddress, [ - coerceScalarToString, - this.coerceScalarToNumberOrError + return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [ + { typeCoercionFunction: coerceScalarToString }, + { typeCoercionFunction: this.coerceScalarToNumberOrError }, ], (text: string, count: number) => { if (count < 0) { return new CellError(ErrorType.VALUE) @@ -151,11 +151,8 @@ export class TextPlugin extends FunctionPlugin { public right(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [ - coerceScalarToString, - this.coerceScalarToNumberOrError - ], [ - undefined, - 1 + { typeCoercionFunction: coerceScalarToString }, + { typeCoercionFunction: this.coerceScalarToNumberOrError, defaultValue: 1 }, ], (text: string, length: number) => { if (length < 0) { return new CellError(ErrorType.VALUE) @@ -168,11 +165,8 @@ export class TextPlugin extends FunctionPlugin { public left(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [ - coerceScalarToString, - this.coerceScalarToNumberOrError - ], [ - undefined, - 1 + { typeCoercionFunction: coerceScalarToString }, + { typeCoercionFunction: this.coerceScalarToNumberOrError, defaultValue: 1 }, ], (text: string, length: number) => { if (length < 0) { return new CellError(ErrorType.VALUE) @@ -183,13 +177,9 @@ export class TextPlugin extends FunctionPlugin { public search(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [ - coerceScalarToString, - coerceScalarToString, - this.coerceScalarToNumberOrError - ], [ - undefined, - undefined, - 1 + { typeCoercionFunction: coerceScalarToString }, + { typeCoercionFunction: coerceScalarToString }, + { typeCoercionFunction: this.coerceScalarToNumberOrError, defaultValue: 1 }, ], (pattern, text: string, startIndex: number) => { if (startIndex < 1 || startIndex > text.length) { return new CellError(ErrorType.VALUE) @@ -211,13 +201,9 @@ export class TextPlugin extends FunctionPlugin { public find(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [ - coerceScalarToString, - coerceScalarToString, - this.coerceScalarToNumberOrError - ], [ - undefined, - undefined, - 1 + { typeCoercionFunction: coerceScalarToString }, + { typeCoercionFunction: coerceScalarToString }, + { typeCoercionFunction: this.coerceScalarToNumberOrError, defaultValue: 1 }, ], (pattern, text: string, startIndex: number) => { if (startIndex < 1 || startIndex > text.length) { return new CellError(ErrorType.VALUE) From 83e6c4c83a7d1a1ce2e6efc4812301c80de27e47 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Wed, 1 Jul 2020 09:43:13 +0200 Subject: [PATCH 13/97] Align with develop --- test/interpreter/function-search.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/interpreter/function-search.spec.ts b/test/interpreter/function-search.spec.ts index 31cfdd52c5..027de54bda 100644 --- a/test/interpreter/function-search.spec.ts +++ b/test/interpreter/function-search.spec.ts @@ -71,7 +71,7 @@ describe('Function SEARCH', () => { ['=SEARCH("b.z", "foobarbaz")'], ['=SEARCH("b.b", "foobarbaz")'], ['=SEARCH(".b", "foobarbaz", 5)'], - ], { useRegularExpresssions: true }) + ], { useRegularExpressions: true }) expect(engine.getCellValue(adr('A1'))).toEqual(1) expect(engine.getCellValue(adr('A2'))).toEqual(4) From 9c02db49b965053006b3f41c5da750dcebe48b4b Mon Sep 17 00:00:00 2001 From: izulin Date: Tue, 21 Jul 2020 00:47:26 +0200 Subject: [PATCH 14/97] string types --- src/interpreter/plugin/FunctionPlugin.ts | 20 ++++++-- src/interpreter/plugin/TextPlugin.ts | 59 +++++++++++++----------- 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index ddc80c3f3a..302016a07a 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -31,8 +31,10 @@ export interface FunctionPluginDefinition { implementedFunctions: ImplementedFunctions, } +export type ArgumentTypes = "string" | "number" + export interface FunctionArgumentDefinition { - typeCoercionFunction: (arg: InternalScalarValue) => InternalScalarValue, + argumentType: string, defaultValue?: InternalScalarValue, } @@ -88,11 +90,11 @@ export abstract class FunctionPlugin { } protected templateWithOneCoercedToNumberArgument(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (arg: number) => InternalScalarValue): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [{ typeCoercionFunction: this.coerceScalarToNumberOrError }], fn) + return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [{ argumentType: "number"}], fn) } protected templateWithOneCoercedToStringArgument(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (arg: string) => InternalScalarValue): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [{ typeCoercionFunction: coerceScalarToString }], fn) + return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [{ argumentType: "string" }], fn) } protected validateTwoNumericArguments(ast: ProcedureAst, formulaAddress: SimpleCellAddress): [number, number] | CellError { @@ -145,8 +147,16 @@ export abstract class FunctionPlugin { return value } - protected coerceScalarToNumberOrError = (arg: InternalScalarValue): number | CellError => this.interpreter.arithmeticHelper.coerceScalarToNumberOrError(arg) + public coerceScalarToNumberOrError = (arg: InternalScalarValue): number | CellError => this.interpreter.arithmeticHelper.coerceScalarToNumberOrError(arg) + public coerceToType(arg: InternalScalarValue, coercedType: ArgumentTypes): InternalScalarValue { + switch(coercedType) { + case 'number': + return this.coerceScalarToNumberOrError(arg) + case 'string': + return coerceScalarToString(arg) + } + } protected coerceArgumentsWithDefaults = ( args: Ast[], formulaAddress: SimpleCellAddress, @@ -163,7 +173,7 @@ export abstract class FunctionPlugin { if (arg instanceof SimpleRangeValue) { return new CellError(ErrorType.VALUE) } - const coercedArg = argumentDefinitions[i].typeCoercionFunction(arg) + const coercedArg = this.coerceToType(arg, argumentDefinitions[i].argumentType as ArgumentTypes) if (coercedArg instanceof CellError) { return coercedArg } diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index d91606df5b..21e03a3db5 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -32,19 +32,41 @@ export class TextPlugin extends FunctionPlugin { method: 'clean' }, 'REPT': { - method: 'rept' + method: 'rept', + parameters: [ + { argumentType: "string" }, + { argumentType: "number" }, + ], }, 'RIGHT': { - method: 'right' + method: 'right', + parameters: [ + { argumentType: "string" }, + { argumentType: "number", defaultValue: 1 }, + ], }, 'LEFT': { - method: 'left' + method: 'left', + parameters: [ + { argumentType: "string" }, + { argumentType: "number", defaultValue: 1 }, + ], }, 'SEARCH': { - method: 'search' + method: 'search', + parameters: [ + { argumentType: "string" }, + { argumentType: "string" }, + { argumentType: "number", defaultValue: 1 }, + ], }, 'FIND': { - method: 'find' + method: 'find', + parameters: [ + { argumentType: "string" }, + { argumentType: "string" }, + { argumentType: "number", defaultValue: 1 }, + ], } } @@ -138,10 +160,7 @@ export class TextPlugin extends FunctionPlugin { } public rept(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [ - { typeCoercionFunction: coerceScalarToString }, - { typeCoercionFunction: this.coerceScalarToNumberOrError }, - ], (text: string, count: number) => { + return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.REPT.parameters, (text: string, count: number) => { if (count < 0) { return new CellError(ErrorType.VALUE) } @@ -150,10 +169,7 @@ export class TextPlugin extends FunctionPlugin { } public right(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [ - { typeCoercionFunction: coerceScalarToString }, - { typeCoercionFunction: this.coerceScalarToNumberOrError, defaultValue: 1 }, - ], (text: string, length: number) => { + return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.RIGHT.parameters, (text: string, length: number) => { if (length < 0) { return new CellError(ErrorType.VALUE) } else if (length === 0) { @@ -164,10 +180,7 @@ export class TextPlugin extends FunctionPlugin { } public left(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [ - { typeCoercionFunction: coerceScalarToString }, - { typeCoercionFunction: this.coerceScalarToNumberOrError, defaultValue: 1 }, - ], (text: string, length: number) => { + return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.LEFT.parameters, (text: string, length: number) => { if (length < 0) { return new CellError(ErrorType.VALUE) } @@ -176,11 +189,7 @@ export class TextPlugin extends FunctionPlugin { } public search(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [ - { typeCoercionFunction: coerceScalarToString }, - { typeCoercionFunction: coerceScalarToString }, - { typeCoercionFunction: this.coerceScalarToNumberOrError, defaultValue: 1 }, - ], (pattern, text: string, startIndex: number) => { + return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.SEARCH.parameters, (pattern, text: string, startIndex: number) => { if (startIndex < 1 || startIndex > text.length) { return new CellError(ErrorType.VALUE) } @@ -200,11 +209,7 @@ export class TextPlugin extends FunctionPlugin { } public find(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [ - { typeCoercionFunction: coerceScalarToString }, - { typeCoercionFunction: coerceScalarToString }, - { typeCoercionFunction: this.coerceScalarToNumberOrError, defaultValue: 1 }, - ], (pattern, text: string, startIndex: number) => { + return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.FIND.parameters, (pattern, text: string, startIndex: number) => { if (startIndex < 1 || startIndex > text.length) { return new CellError(ErrorType.VALUE) } From eaaae5e58ea3ad64968f38749f7d60a7dd8142f9 Mon Sep 17 00:00:00 2001 From: izulin Date: Tue, 21 Jul 2020 00:49:51 +0200 Subject: [PATCH 15/97] linter --- src/interpreter/plugin/FunctionPlugin.ts | 6 +++--- src/interpreter/plugin/TextPlugin.ts | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 302016a07a..ecb425d545 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -31,7 +31,7 @@ export interface FunctionPluginDefinition { implementedFunctions: ImplementedFunctions, } -export type ArgumentTypes = "string" | "number" +export type ArgumentTypes = 'string' | 'number' export interface FunctionArgumentDefinition { argumentType: string, @@ -90,11 +90,11 @@ export abstract class FunctionPlugin { } protected templateWithOneCoercedToNumberArgument(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (arg: number) => InternalScalarValue): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [{ argumentType: "number"}], fn) + return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [{ argumentType: 'number'}], fn) } protected templateWithOneCoercedToStringArgument(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (arg: string) => InternalScalarValue): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [{ argumentType: "string" }], fn) + return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [{ argumentType: 'string' }], fn) } protected validateTwoNumericArguments(ast: ProcedureAst, formulaAddress: SimpleCellAddress): [number, number] | CellError { diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index 21e03a3db5..c551e7ad30 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -34,38 +34,38 @@ export class TextPlugin extends FunctionPlugin { 'REPT': { method: 'rept', parameters: [ - { argumentType: "string" }, - { argumentType: "number" }, + { argumentType: 'string' }, + { argumentType: 'number' }, ], }, 'RIGHT': { method: 'right', parameters: [ - { argumentType: "string" }, - { argumentType: "number", defaultValue: 1 }, + { argumentType: 'string' }, + { argumentType: 'number', defaultValue: 1 }, ], }, 'LEFT': { method: 'left', parameters: [ - { argumentType: "string" }, - { argumentType: "number", defaultValue: 1 }, + { argumentType: 'string' }, + { argumentType: 'number', defaultValue: 1 }, ], }, 'SEARCH': { method: 'search', parameters: [ - { argumentType: "string" }, - { argumentType: "string" }, - { argumentType: "number", defaultValue: 1 }, + { argumentType: 'string' }, + { argumentType: 'string' }, + { argumentType: 'number', defaultValue: 1 }, ], }, 'FIND': { method: 'find', parameters: [ - { argumentType: "string" }, - { argumentType: "string" }, - { argumentType: "number", defaultValue: 1 }, + { argumentType: 'string' }, + { argumentType: 'string' }, + { argumentType: 'number', defaultValue: 1 }, ], } } From b8c2d3f8f236b2307e2cd92e1f340fb1c4c4d334 Mon Sep 17 00:00:00 2001 From: izulin Date: Tue, 21 Jul 2020 16:07:58 +0200 Subject: [PATCH 16/97] split fixed --- src/interpreter/plugin/TextPlugin.ts | 34 +++++++++------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index c551e7ad30..09eeb375a8 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -18,6 +18,10 @@ export class TextPlugin extends FunctionPlugin { }, 'SPLIT': { method: 'split', + parameters: [ + { argumentType: 'string' }, + { argumentType: 'number' }, + ], }, 'LEN': { method: 'len' @@ -107,31 +111,15 @@ export class TextPlugin extends FunctionPlugin { * @param formulaAddress */ public split(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length !== 2) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - const stringArg = ast.args[0] - const indexArg = ast.args[1] + return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.SPLIT.parameters, (stringToSplit: string, indexToUse: number) => { + const splittedString = stringToSplit.split(' ') - const stringToSplit = this.evaluateAst(stringArg, formulaAddress) - if (typeof stringToSplit !== 'string') { - return new CellError(ErrorType.VALUE) - } - const indexToUse = this.evaluateAst(indexArg, formulaAddress) - if (typeof indexToUse !== 'number') { - return new CellError(ErrorType.VALUE) - } - - const splittedString = stringToSplit.split(' ') - - if (indexToUse > splittedString.length || indexToUse < 0) { - return new CellError(ErrorType.VALUE) - } + if (indexToUse >= splittedString.length || indexToUse < 0) { + return new CellError(ErrorType.VALUE) + } - return splittedString[indexToUse] + return splittedString[indexToUse] + }) } public len(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { From 878acd5826a0eeea83d4677aa2f78b836347fee3 Mon Sep 17 00:00:00 2001 From: izulin Date: Tue, 21 Jul 2020 23:30:13 +0200 Subject: [PATCH 17/97] . --- src/interpreter/plugin/FunctionPlugin.ts | 28 +++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index ecb425d545..7f74873369 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -157,6 +157,7 @@ export abstract class FunctionPlugin { return coerceScalarToString(arg) } } + protected coerceArgumentsWithDefaults = ( args: Ast[], formulaAddress: SimpleCellAddress, @@ -168,6 +169,31 @@ export abstract class FunctionPlugin { } const coercedArguments: InternalScalarValue[] = [] + + for (let i = 0; i < argumentDefinitions.length; ++i) { + const arg = this.evaluateArgOrDefault(formulaAddress, args[i], argumentDefinitions[i].defaultValue) + if (arg instanceof SimpleRangeValue) { + return new CellError(ErrorType.VALUE) + } + const coercedArg = this.coerceToType(arg, argumentDefinitions[i].argumentType as ArgumentTypes) + if (coercedArg instanceof CellError) { + return coercedArg + } + coercedArguments.push(coercedArg) + } + + return fn(...coercedArguments) + } + + + protected coerceRepeatedArgumentsWithDefaults = ( + args: Ast[], + formulaAddress: SimpleCellAddress, + argumentDefinitions: FunctionArgumentDefinition[], + fn: (...arg: any) => InternalScalarValue + ) => { + const coercedArguments: InternalScalarValue[] = [] + for (let i = 0; i < argumentDefinitions.length; ++i) { const arg = this.evaluateArgOrDefault(formulaAddress, args[i], argumentDefinitions[i].defaultValue) if (arg instanceof SimpleRangeValue) { @@ -187,6 +213,6 @@ export abstract class FunctionPlugin { if (argAst !== undefined) { return this.evaluateAst(argAst, formulaAddress) } - return defaultValue || new CellError(ErrorType.NA) + return defaultValue ?? new CellError(ErrorType.NA) } } From 6501069b66f688b4f4e4fa4bdbacd55d72aa93bf Mon Sep 17 00:00:00 2001 From: izulin Date: Wed, 22 Jul 2020 00:25:13 +0200 Subject: [PATCH 18/97] . --- src/interpreter/plugin/FunctionPlugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 7f74873369..8ab6ead916 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -194,7 +194,7 @@ export abstract class FunctionPlugin { ) => { const coercedArguments: InternalScalarValue[] = [] - for (let i = 0; i < argumentDefinitions.length; ++i) { + for (let i = 0; i < Math.max(argumentDefinitions.length, args.length); ++i) { const arg = this.evaluateArgOrDefault(formulaAddress, args[i], argumentDefinitions[i].defaultValue) if (arg instanceof SimpleRangeValue) { return new CellError(ErrorType.VALUE) From 3139f737bb5c56e2cb435b3715e7b1504d6f782d Mon Sep 17 00:00:00 2001 From: izulin Date: Wed, 22 Jul 2020 12:51:54 +0200 Subject: [PATCH 19/97] financial simpler --- src/interpreter/plugin/FinancialPlugin.ts | 196 ++++------------------ src/interpreter/plugin/FunctionPlugin.ts | 8 +- src/interpreter/plugin/TextPlugin.ts | 12 +- 3 files changed, 44 insertions(+), 172 deletions(-) diff --git a/src/interpreter/plugin/FinancialPlugin.ts b/src/interpreter/plugin/FinancialPlugin.ts index edaf460a16..e31d644d4c 100644 --- a/src/interpreter/plugin/FinancialPlugin.ts +++ b/src/interpreter/plugin/FinancialPlugin.ts @@ -12,190 +12,62 @@ export class FinancialPlugin extends FunctionPlugin { public static implementedFunctions = { 'PMT': { method: 'pmt', + parameters: [ + { argumentType: 'number' }, + { argumentType: 'number' }, + { argumentType: 'number' }, + { argumentType: 'number', defaultValue: 0 }, + { argumentType: 'number', defaultValue: 0 }, + ], }, 'IPMT': { method: 'ipmt', + parameters: [ + { argumentType: 'number' }, + { argumentType: 'number' }, + { argumentType: 'number' }, + { argumentType: 'number' }, + { argumentType: 'number', defaultValue: 0 }, + { argumentType: 'number', defaultValue: 0 }, + ], }, 'PPMT': { method: 'ppmt', + parameters: [ + { argumentType: 'number' }, + { argumentType: 'number' }, + { argumentType: 'number' }, + { argumentType: 'number' }, + { argumentType: 'number', defaultValue: 0 }, + { argumentType: 'number', defaultValue: 0 }, + ], }, 'FV': { method: 'fv', + parameters: [ + { argumentType: 'number' }, + { argumentType: 'number' }, + { argumentType: 'number' }, + { argumentType: 'number', defaultValue: 0 }, + { argumentType: 'number', defaultValue: 0 }, + ], }, } - private template5(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (arg1: number, arg2: number, arg3: number, arg4: number, arg5: number) => number): InternalScalarValue { - if (ast.args.length < 3 || ast.args.length > 5) { - return new CellError(ErrorType.NA) - } - - if(ast.args[0].type === AstNodeType.EMPTY || ast.args[1].type === AstNodeType.EMPTY || ast.args[2].type === AstNodeType.EMPTY) { - return new CellError(ErrorType.NUM) - } - - let arg1 = this.evaluateAst(ast.args[0], formulaAddress) - if(arg1 instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - let arg2 = this.evaluateAst(ast.args[1], formulaAddress) - if(arg2 instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - let arg3 = this.evaluateAst(ast.args[2], formulaAddress) - if(arg3 instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - let arg4, arg5 - if(ast.args.length < 4 || ast.args[3].type === AstNodeType.EMPTY) { - arg4 = 0 - } else { - arg4 = this.evaluateAst(ast.args[3], formulaAddress) - } - - if(arg4 instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - if(ast.args.length < 5 || ast.args[4].type === AstNodeType.EMPTY) { - arg5 = 0 - } else { - arg5 = this.evaluateAst(ast.args[4], formulaAddress) - } - - if(arg5 instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - - arg1 = this.coerceScalarToNumberOrError(arg1) - if(arg1 instanceof CellError) { - return arg1 - } - - arg2 = this.coerceScalarToNumberOrError(arg2) - if(arg2 instanceof CellError) { - return arg2 - } - - arg3 = this.coerceScalarToNumberOrError(arg3) - if(arg3 instanceof CellError) { - return arg3 - } - - arg4 = this.coerceScalarToNumberOrError(arg4) - if(arg4 instanceof CellError) { - return arg4 - } - - arg5 = this.coerceScalarToNumberOrError(arg5) - if(arg5 instanceof CellError) { - return arg5 - } - - return fn(arg1, arg2, arg3, arg4, arg5) - } - - private template6(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (arg1: number, arg2: number, arg3: number, arg4: number, arg5: number, arg6: number) => number): InternalScalarValue { - if (ast.args.length < 4 || ast.args.length > 6) { - return new CellError(ErrorType.NA) - } - - if(ast.args[0].type === AstNodeType.EMPTY || ast.args[1].type === AstNodeType.EMPTY || ast.args[2].type === AstNodeType.EMPTY || ast.args[3].type === AstNodeType.EMPTY) { - return new CellError(ErrorType.NUM) - } - - let arg1 = this.evaluateAst(ast.args[0], formulaAddress) - if(arg1 instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - let arg2 = this.evaluateAst(ast.args[1], formulaAddress) - if(arg2 instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - let arg3 = this.evaluateAst(ast.args[2], formulaAddress) - if(arg3 instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - let arg4 = this.evaluateAst(ast.args[3], formulaAddress) - if(arg4 instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - let arg5, arg6 - if(ast.args.length < 5 || ast.args[4].type === AstNodeType.EMPTY) { - arg5 = 0 - } else { - arg5 = this.evaluateAst(ast.args[4], formulaAddress) - } - - if(arg5 instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - if(ast.args.length < 6 || ast.args[5].type === AstNodeType.EMPTY) { - arg6 = 0 - } else { - arg6 = this.evaluateAst(ast.args[5], formulaAddress) - } - - if(arg6 instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - - arg1 = this.coerceScalarToNumberOrError(arg1) - if(arg1 instanceof CellError) { - return arg1 - } - - arg2 = this.coerceScalarToNumberOrError(arg2) - if(arg2 instanceof CellError) { - return arg2 - } - - arg3 = this.coerceScalarToNumberOrError(arg3) - if(arg3 instanceof CellError) { - return arg3 - } - - arg4 = this.coerceScalarToNumberOrError(arg4) - if(arg4 instanceof CellError) { - return arg4 - } - - arg5 = this.coerceScalarToNumberOrError(arg5) - if(arg5 instanceof CellError) { - return arg5 - } - - arg6 = this.coerceScalarToNumberOrError(arg6) - if(arg6 instanceof CellError) { - return arg6 - } - - return fn(arg1, arg2, arg3, arg4, arg5, arg6) - } - public pmt(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.template5(ast, formulaAddress, pmtCore) + return this.runFunctionWithDefaults(ast.args, formulaAddress, FinancialPlugin.implementedFunctions.PMT.parameters, pmtCore) } public ipmt(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.template6(ast, formulaAddress, ipmtCore) + return this.runFunctionWithDefaults(ast.args, formulaAddress, FinancialPlugin.implementedFunctions.IPMT.parameters, ipmtCore) } public ppmt(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.template6(ast, formulaAddress, ppmtCore) + return this.runFunctionWithDefaults(ast.args, formulaAddress, FinancialPlugin.implementedFunctions.PPMT.parameters, ppmtCore) } public fv(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.template5(ast, formulaAddress, fvCore) + return this.runFunctionWithDefaults(ast.args, formulaAddress, FinancialPlugin.implementedFunctions.FV.parameters, fvCore) } } diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 8ab6ead916..062d23b8c1 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -90,11 +90,11 @@ export abstract class FunctionPlugin { } protected templateWithOneCoercedToNumberArgument(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (arg: number) => InternalScalarValue): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [{ argumentType: 'number'}], fn) + return this.runFunctionWithDefaults(ast.args, formulaAddress, [{ argumentType: 'number'}], fn) } protected templateWithOneCoercedToStringArgument(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (arg: string) => InternalScalarValue): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [{ argumentType: 'string' }], fn) + return this.runFunctionWithDefaults(ast.args, formulaAddress, [{ argumentType: 'string' }], fn) } protected validateTwoNumericArguments(ast: ProcedureAst, formulaAddress: SimpleCellAddress): [number, number] | CellError { @@ -158,7 +158,7 @@ export abstract class FunctionPlugin { } } - protected coerceArgumentsWithDefaults = ( + protected runFunctionWithDefaults = ( args: Ast[], formulaAddress: SimpleCellAddress, argumentDefinitions: FunctionArgumentDefinition[], @@ -186,7 +186,7 @@ export abstract class FunctionPlugin { } - protected coerceRepeatedArgumentsWithDefaults = ( + protected runFunctionWithRepeatedArg = ( args: Ast[], formulaAddress: SimpleCellAddress, argumentDefinitions: FunctionArgumentDefinition[], diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index 09eeb375a8..d244f575e1 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -111,7 +111,7 @@ export class TextPlugin extends FunctionPlugin { * @param formulaAddress */ public split(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.SPLIT.parameters, (stringToSplit: string, indexToUse: number) => { + return this.runFunctionWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.SPLIT.parameters, (stringToSplit: string, indexToUse: number) => { const splittedString = stringToSplit.split(' ') if (indexToUse >= splittedString.length || indexToUse < 0) { @@ -148,7 +148,7 @@ export class TextPlugin extends FunctionPlugin { } public rept(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.REPT.parameters, (text: string, count: number) => { + return this.runFunctionWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.REPT.parameters, (text: string, count: number) => { if (count < 0) { return new CellError(ErrorType.VALUE) } @@ -157,7 +157,7 @@ export class TextPlugin extends FunctionPlugin { } public right(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.RIGHT.parameters, (text: string, length: number) => { + return this.runFunctionWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.RIGHT.parameters, (text: string, length: number) => { if (length < 0) { return new CellError(ErrorType.VALUE) } else if (length === 0) { @@ -168,7 +168,7 @@ export class TextPlugin extends FunctionPlugin { } public left(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.LEFT.parameters, (text: string, length: number) => { + return this.runFunctionWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.LEFT.parameters, (text: string, length: number) => { if (length < 0) { return new CellError(ErrorType.VALUE) } @@ -177,7 +177,7 @@ export class TextPlugin extends FunctionPlugin { } public search(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.SEARCH.parameters, (pattern, text: string, startIndex: number) => { + return this.runFunctionWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.SEARCH.parameters, (pattern, text: string, startIndex: number) => { if (startIndex < 1 || startIndex > text.length) { return new CellError(ErrorType.VALUE) } @@ -197,7 +197,7 @@ export class TextPlugin extends FunctionPlugin { } public find(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.FIND.parameters, (pattern, text: string, startIndex: number) => { + return this.runFunctionWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.FIND.parameters, (pattern, text: string, startIndex: number) => { if (startIndex < 1 || startIndex > text.length) { return new CellError(ErrorType.VALUE) } From 0ff0807653afdae3e57f376341ab0866f9ee9c0d Mon Sep 17 00:00:00 2001 From: izulin Date: Wed, 22 Jul 2020 14:12:17 +0200 Subject: [PATCH 20/97] repeated args --- src/interpreter/plugin/FunctionPlugin.ts | 22 +++++++++++++++------- src/interpreter/plugin/TextPlugin.ts | 23 ++++++----------------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 062d23b8c1..157b629002 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -185,25 +185,33 @@ export abstract class FunctionPlugin { return fn(...coercedArguments) } - protected runFunctionWithRepeatedArg = ( args: Ast[], formulaAddress: SimpleCellAddress, argumentDefinitions: FunctionArgumentDefinition[], + repeatedArgs: number, fn: (...arg: any) => InternalScalarValue ) => { + const scalarValues: InternalScalarValue[] = [...this.iterateOverScalarValues(args, formulaAddress)] const coercedArguments: InternalScalarValue[] = [] - for (let i = 0; i < Math.max(argumentDefinitions.length, args.length); ++i) { - const arg = this.evaluateArgOrDefault(formulaAddress, args[i], argumentDefinitions[i].defaultValue) - if (arg instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - const coercedArg = this.coerceToType(arg, argumentDefinitions[i].argumentType as ArgumentTypes) + let j = 0 + let i = 0 + while(true) { + const arg = scalarValues[i] ?? argumentDefinitions[j].defaultValue ?? new CellError(ErrorType.NA) + const coercedArg = this.coerceToType(arg, argumentDefinitions[j].argumentType as ArgumentTypes) if (coercedArg instanceof CellError) { return coercedArg } coercedArguments.push(coercedArg) + j++ + i++ + if(i >= scalarValues.length && j === argumentDefinitions.length) { + break + } + if(j===argumentDefinitions.length) { + j -= repeatedArgs + } } return fn(...coercedArguments) diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index d244f575e1..1f85b6c1b1 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -15,6 +15,9 @@ export class TextPlugin extends FunctionPlugin { public static implementedFunctions = { 'CONCATENATE': { method: 'concatenate', + parameters: [ + { argumentType: 'string'} + ] }, 'SPLIT': { method: 'split', @@ -83,23 +86,9 @@ export class TextPlugin extends FunctionPlugin { * @param formulaAddress */ public concatenate(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length == 0) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - let result = '' - for (const value of this.iterateOverScalarValues(ast.args, formulaAddress)) { - const coercedValue = coerceScalarToString(value) - if (coercedValue instanceof CellError) { - return value - } else { - result = result.concat(coercedValue) - } - } - return result + return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, TextPlugin.implementedFunctions.CONCATENATE.parameters, 1, (...args) => { + return ''.concat(...args) + }) } /** From fb9e6153428b37f013f4a1f7bae0cc5f922b99fa Mon Sep 17 00:00:00 2001 From: izulin Date: Wed, 22 Jul 2020 14:35:15 +0200 Subject: [PATCH 21/97] translations --- src/i18n/languages/csCZ.ts | 4 ++++ src/i18n/languages/daDK.ts | 4 ++++ src/i18n/languages/deDE.ts | 6 +++++- src/i18n/languages/enGB.ts | 4 ++++ src/i18n/languages/esES.ts | 4 ++++ src/i18n/languages/fiFI.ts | 4 ++++ src/i18n/languages/frFR.ts | 4 ++++ src/i18n/languages/huHU.ts | 4 ++++ src/i18n/languages/itIT.ts | 4 ++++ src/i18n/languages/nbNO.ts | 4 ++++ src/i18n/languages/nlNL.ts | 4 ++++ src/i18n/languages/plPL.ts | 4 ++++ src/i18n/languages/ptPT.ts | 4 ++++ src/i18n/languages/ruRU.ts | 4 ++++ src/i18n/languages/svSE.ts | 4 ++++ src/i18n/languages/trTR.ts | 4 ++++ 16 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index 5a9ea44498..fbf500a5c0 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -69,11 +69,13 @@ const dictionary: RawTranslationPackage = { EXP: 'EXP', FALSE: 'NEPRAVDA', FIND: 'NAJÍT', + FV: 'BUDHODNOTA', IF: 'KDYŽ', IFERROR: 'IFERROR', IFNA: 'IFNA', INDEX: 'INDEX', INT: 'CELÁ.ČÁST', + IPMT: 'PLATBA.ÚROK', ISBLANK: 'JE.PRÁZDNÉ', ISERROR: 'JE.CHYBHODN', ISEVEN: 'ISEVEN', @@ -103,7 +105,9 @@ const dictionary: RawTranslationPackage = { OFFSET: 'POSUN', OR: 'NEBO', PI: 'PI', + PMT: 'PLATBA', POWER: 'POWER', + PPMT: 'PLATBA.ZÁKLAD', PROPER: 'VELKÁ2', RADIANS: 'RADIANS', RAND: 'NÁHČÍSLO', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 42533a25ca..f433a47d37 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -69,11 +69,13 @@ const dictionary: RawTranslationPackage = { EXP: 'EKSP', FALSE: 'FALSE', FIND: 'FIND', + FV: 'FV', IF: 'HVIS', IFERROR: 'HVIS.FEJL', IFNA: 'HVISIT', INDEX: 'INDEKS', INT: 'HELTAL', + IPMT: 'R.YDELSE', ISBLANK: 'ER.TOM', ISERROR: 'ER.FEJL', ISEVEN: 'ER.LIGE', @@ -103,7 +105,9 @@ const dictionary: RawTranslationPackage = { OFFSET: 'FORSKYDNING', OR: 'ELLER', PI: 'PI', + PMT: 'YDELSE', POWER: 'POTENS', + PPMT: 'H.YDELSE', PROPER: 'STORT.FORBOGSTAV', RADIANS: 'RADIANER', RAND: 'SLUMP', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index 6f3228547b..0ad4eb8aca 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -69,11 +69,13 @@ const dictionary: RawTranslationPackage = { EXP: 'EXP', FALSE: 'FALSCH', FIND: 'FINDEN', + FV: 'ZW', IF: 'WENN', IFERROR: 'WENNFEHLER', IFNA: 'WENNNV', INDEX: 'INDEX', INT: 'GANZZAHL', + IPMT: 'ZINSZ', ISBLANK: 'ISTLEER', ISERROR: 'ISTFEHLER', ISEVEN: 'ISTGERADE', @@ -103,7 +105,9 @@ const dictionary: RawTranslationPackage = { OFFSET: 'BEREICH.VERSCHIEBEN', OR: 'ODER', PI: 'PI', + PMT: 'RMZ', POWER: 'POTENZ', + PPMT: 'KAPZ', PROPER: 'GROSS2', RADIANS: 'BOGENMASS', RAND: 'ZUFALLSZAHL', @@ -139,4 +143,4 @@ const dictionary: RawTranslationPackage = { }, } -export default dictionary \ No newline at end of file +export default dictionary diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index 6b259115c3..1651926176 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -69,11 +69,13 @@ const dictionary: RawTranslationPackage = { EXP: 'EXP', FALSE: 'FALSE', FIND: 'FIND', + FV: 'FV', IF: 'IF', IFERROR: 'IFERROR', IFNA: 'IFNA', INDEX: 'INDEX', INT: 'INT', + IPMT: 'IPMT', ISBLANK: 'ISBLANK', ISERROR: 'ISERROR', ISEVEN: 'ISEVEN', @@ -103,6 +105,8 @@ const dictionary: RawTranslationPackage = { OFFSET: 'OFFSET', OR: 'OR', PI: 'PI', + PMT: 'PMT', + PPMT: 'PPMT', POWER: 'POWER', PROPER: 'PROPER', RADIANS: 'RADIANS', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index 85c9b3fc88..7224293181 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -69,11 +69,13 @@ export const dictionary: RawTranslationPackage = { EXP: 'EXP', FALSE: 'FALSO', FIND: 'ENCONTRAR', + FV: 'VF', IF: 'SI', IFERROR: 'SI.ERROR', IFNA: 'IFNA', INDEX: 'INDICE', INT: 'ENTERO', + IPMT: 'PAGOINT', ISBLANK: 'ESBLANCO', ISERROR: 'ESERROR', ISEVEN: 'ES.PAR', @@ -103,7 +105,9 @@ export const dictionary: RawTranslationPackage = { OFFSET: 'DESREF', OR: 'O', PI: 'PI', + PMT: 'PAGO', POWER: 'POTENCIA', + PPMT: 'PAGOPRIN', PROPER: 'NOMPROPIO', RADIANS: 'RADIANES', RAND: 'ALEATORIO', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 179f52870a..1e1374fa08 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -69,11 +69,13 @@ const dictionary: RawTranslationPackage = { EXP: 'EKSPONENTTI', FALSE: 'EPÄTOSI', FIND: 'ETSI', + FV: 'TULEVA.ARVO', IF: 'JOS', IFERROR: 'JOSVIRHE', IFNA: 'JOSPUUTTUU', INDEX: 'INDEKSI', INT: 'KOKONAISLUKU', + IPMT: 'IPMT', ISBLANK: 'ONTYHJÄ', ISERROR: 'ONVIRHE', ISEVEN: 'ONPARILLINEN', @@ -103,7 +105,9 @@ const dictionary: RawTranslationPackage = { OFFSET: 'SIIRTYMÄ', OR: 'TAI', PI: 'PII', + PMT: 'MAKSU', POWER: 'POTENSSI', + PPMT: 'PPMT', PROPER: 'ERISNIMI', RADIANS: 'RADIAANIT', RAND: 'SATUNNAISLUKU', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index 0c0cdd76f1..bd8a20cf41 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -69,11 +69,13 @@ const dictionary: RawTranslationPackage = { EXP: 'EXP', FALSE: 'FAUX', FIND: 'TROUVE', + FV: 'VC', IF: 'SI', IFERROR: 'SIERREUR', IFNA: 'SI.NON.DISP', INDEX: 'INDEX', INT: 'ENT', + IPMT: 'INTPER', ISBLANK: 'ESTVIDE', ISERROR: 'ESTERREUR', ISEVEN: 'EST.PAIR', @@ -103,7 +105,9 @@ const dictionary: RawTranslationPackage = { OFFSET: 'DECALER', OR: 'OU', PI: 'PI', + PMT: 'VPM', POWER: 'PUISSANCE', + PPMT: 'PRINCPER', PROPER: 'NOMPROPRE', RADIANS: 'RADIANS', RAND: 'ALEA', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index e6e256eec8..9865ce32e4 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -69,11 +69,13 @@ const dictionary: RawTranslationPackage = { EXP: 'KITEVŐ', FALSE: 'HAMIS', FIND: 'SZÖVEG.TALÁL', + FV: 'JBÉ', IF: 'HA', IFERROR: 'HAHIBA', IFNA: 'HAHIÁNYZIK', INDEX: 'INDEX', INT: 'INT', + IPMT: 'RRÉSZLET', ISBLANK: 'ÜRES', ISERROR: 'HIBÁS', ISEVEN: 'PÁROSE', @@ -103,7 +105,9 @@ const dictionary: RawTranslationPackage = { OFFSET: 'ELTOLÁS', OR: 'VAGY', PI: 'PI', + PMT: 'RÉSZLET', POWER: 'HATVÁNY', + PPMT: 'PRÉSZLET', PROPER: 'TNÉV', RADIANS: 'RADIÁN', RAND: 'VÉL', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index ab30e4a621..98d7c71c14 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -69,11 +69,13 @@ const dictionary: RawTranslationPackage = { EXP: 'EXP', FALSE: 'FALSO', FIND: 'TROVA', + FV: 'VAL.FUT', IF: 'SE', IFERROR: 'SE.ERRORE', IFNA: 'SE.NON.DISP.', INDEX: 'INDICE', INT: 'INT', + IPMT: 'INTERESSI', ISBLANK: 'VAL.VUOTO', ISERROR: 'VAL.ERRORE', ISEVEN: 'VAL.PARI', @@ -103,7 +105,9 @@ const dictionary: RawTranslationPackage = { OFFSET: 'SCARTO', OR: 'O', PI: 'PI.GRECO', + PMT: 'RATA', POWER: 'POTENZA', + PPMT: 'P.RATA', PROPER: 'MAIUSC.INIZ', RADIANS: 'RADIANTI', RAND: 'CASUALE', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index dad06fd788..f3f0253234 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -69,11 +69,13 @@ const dictionary: RawTranslationPackage = { EXP: 'EKSP', FALSE: 'USANN', FIND: 'FINN', + FV: 'SLUTTVERDI', IF: 'HVIS', IFERROR: 'HVISFEIL', IFNA: 'HVIS.IT', INDEX: 'INDEKS', INT: 'HELTALL', + IPMT: 'RAVDRAG', ISBLANK: 'ERTOM', ISERROR: 'ERFEIL', ISEVEN: 'ERPARTALL', @@ -103,7 +105,9 @@ const dictionary: RawTranslationPackage = { OFFSET: 'FORSKYVNING', OR: 'ELLER', PI: 'PI', + PMT: 'AVDRAG', POWER: 'OPPHØYD.I', + PPMT: 'AMORT', PROPER: 'STOR.FORBOKSTAV', RADIANS: 'RADIANER', RAND: 'TILFELDIG', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index e7539646d1..b564591d23 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -69,11 +69,13 @@ const dictionary: RawTranslationPackage = { EXP: 'EXP', FALSE: 'ONWAAR', FIND: 'VIND.ALLES', + FV: 'TW', IF: 'ALS', IFERROR: 'ALS.FOUT', IFNA: 'ALS.NB', INDEX: 'INDEX', INT: 'INTEGER', + IPMT: 'IBET', ISBLANK: 'ISLEEG', ISERROR: 'ISFOUT', ISEVEN: 'IS.EVEN', @@ -103,7 +105,9 @@ const dictionary: RawTranslationPackage = { OFFSET: 'VERSCHUIVING', OR: 'OF', PI: 'PI', + PMT: 'BET', POWER: 'MACHT', + PPMT: 'PBET', PROPER: 'BEGINLETTERS', RADIANS: 'RADIALEN', RAND: 'ASELECT', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index 458650fdff..a20f60c698 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -69,11 +69,13 @@ const dictionary: RawTranslationPackage = { EXP: 'EXP', FALSE: 'FAŁSZ', FIND: 'ZNAJDŹ', + FV: 'FV', IF: 'JEŻELI', IFERROR: 'JEŻELI.BŁĄD', IFNA: 'JEŻELI.ND', INDEX: 'INDEKS', INT: 'ZAOKR.DO.CAŁK', + IPMT: 'IPMT', ISBLANK: 'CZY.PUSTA', ISERROR: 'CZY.BŁĄD', ISEVEN: 'CZY.PARZYSTE', @@ -103,7 +105,9 @@ const dictionary: RawTranslationPackage = { OFFSET: 'PRZESUNIĘCIE', OR: 'LUB', PI: 'PI', + PMT: 'PMT', POWER: 'POTĘGA', + PPMT: 'PPMT', PROPER: 'Z.WIELKIEJ.LITERY', RADIANS: 'RADIANY', RAND: 'LOSUJ', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index 66ec685dcf..04d7b85581 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -69,11 +69,13 @@ const dictionary: RawTranslationPackage = { EXP: 'EXP', FALSE: 'FALSO', FIND: 'PROCURAR', + FV: 'VF', IF: 'SE', IFERROR: 'SEERRO', IFNA: 'SENA', INDEX: 'ÍNDICE', INT: 'INT', + IPMT: 'IPGTO', ISBLANK: 'ÉCÉL.VAZIA', ISERROR: 'ÉERROS', ISEVEN: 'ÉPAR', @@ -103,7 +105,9 @@ const dictionary: RawTranslationPackage = { OFFSET: 'DESLOC', OR: 'OU', PI: 'PI', + PMT: 'PGTO', POWER: 'POTÊNCIA', + PPMT: 'PPGTO', PROPER: 'PRI.MAIÚSCULA', RADIANS: 'RADIANOS', RAND: 'ALEATÓRIO', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index f62b9058bb..b87adcb7b8 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -69,11 +69,13 @@ const dictionary: RawTranslationPackage = { EXP: 'EXP', FALSE: 'ЛОЖЬ', FIND: 'НАЙТИ', + FV: 'БС', IF: 'ЕСЛИ', IFERROR: 'ЕСЛИОШИБКА', IFNA: 'ЕСНД', INDEX: 'ИНДЕКС', INT: 'ЦЕЛОЕ', + IPMT: 'ПРПЛТ', ISBLANK: 'ЕПУСТО', ISERROR: 'ЕОШИБКА', ISEVEN: 'ЕЧЁТН', @@ -103,7 +105,9 @@ const dictionary: RawTranslationPackage = { OFFSET: 'СМЕЩ', OR: 'ИЛИ', PI: 'ПИ', + PMT: 'ПЛТ', POWER: 'СТЕПЕНЬ', + PPMT: 'ОСПЛТ', PROPER: 'ПРОПНАЧ', RADIANS: 'РАДИАНЫ', RAND: 'СЛЧИС', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index 52865fcd76..214af6eb33 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -69,11 +69,13 @@ const dictionary: RawTranslationPackage = { EXP: 'EXP', FALSE: 'FALSKT', FIND: 'HITTA', + FV: 'SLUTVÄRDE', IF: 'OM', IFERROR: 'OMFEL', IFNA: 'IFNA', INDEX: 'INDEX', INT: 'HELTAL', + IPMT: 'RBETALNING', ISBLANK: 'ÄRTOM', ISERROR: 'ÄRFEL', ISEVEN: 'ÄRJÄMN', @@ -103,7 +105,9 @@ const dictionary: RawTranslationPackage = { OFFSET: 'FÖRSKJUTNING', OR: 'ELLER', PI: 'PI', + PMT: 'BETALNING', POWER: 'UPPHÖJT.TILL', + PPMT: 'AMORT', PROPER: 'INITIAL', RADIANS: 'RADIANER', RAND: 'SLUMP', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index 7fa5ad112e..7d07640413 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -69,11 +69,13 @@ const dictionary: RawTranslationPackage = { EXP: 'ÜS', FALSE: 'YANLIŞ', FIND: 'BUL', + FV: 'GD', IF: 'EĞER', IFERROR: 'EĞERHATA', IFNA: 'EĞERYOKSA', INDEX: 'İNDİS', INT: 'TAMSAYI', + IPMT: 'FAİZTUTARI', ISBLANK: 'EBOŞSA', ISERROR: 'EHATALIYSA', ISEVEN: 'ÇİFTMİ', @@ -103,7 +105,9 @@ const dictionary: RawTranslationPackage = { OFFSET: 'KAYDIR', OR: 'VEYA', PI: 'Pİ', + PMT: 'DEVRESEL_ÖDEME', POWER: 'KUVVET', + PPMT: 'ANA_PARA_ÖDEMESİ', PROPER: 'YAZIM.DÜZENİ', RADIANS: 'RADYAN', RAND: 'S_SAYI_ÜRET', From 2692dbbb40abb37459b45519a6c319f6791b5039 Mon Sep 17 00:00:00 2001 From: izulin Date: Wed, 22 Jul 2020 14:54:12 +0200 Subject: [PATCH 22/97] changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index daab4c0d23..cededbcc3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- Added helper functions for keeping track of cell/range dependencies: getCellPrecedents and getCellDependents. (#441) +- 11 text functions CONCATENATE, SPLIT, LEN, TRIM, PROPER, CLEAN, REPT, RIGHT, LEFT, SEARCH, FIND. +- helper methods for keeping track of cell/range dependencies: getCellPrecedents and getCellDependents. (#441) - 4 financial functions FV, PMT, PPMT, IPMT. ## [0.1.3] - 2020-07-21 From 28bf8a8a3ab33d85ea305c6fdafb0a13d6b4ca7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Uzna=C5=84ski?= <40573492+izulin@users.noreply.github.com> Date: Wed, 22 Jul 2020 14:56:54 +0200 Subject: [PATCH 23/97] Update built-in-functions.md reorder --- docs/guide/built-in-functions.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index 3db556a714..29d18ef9d6 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -75,7 +75,6 @@ lets you design your own [custom functions](custom-functions). | BITOR | Engineering | Returns a bitwise logical "or" of the parameters. | BITOR(Number1; Number2) | | BITRSHIFT | Engineering | Shifts a number right by n bits. | BITRSHIFT(Number; Shift) | | BITXOR | Engineering | Returns a bitwise logical "exclusive or" of the parameters. | BITXOR(Number1; Number2) | -| CHOOSE | Lookup and reference | Uses an index to return a value from a list of up to 30 values.| CHOOSE(Index; Value1; ...; Value30) | | DEC2BIN | Engineering | Returns the binary number for the decimal number entered between –512 and 511. | DEC2BIN(Number; Places) | | DEC2HEX | Engineering | Returns the hexadecimal number for the decimal number entered. | DEC2HEX(Number; Places) | | DEC2OCT | Engineering | Returns the octal number for the decimal number entered. | DEC2OCT(Number; Places) | @@ -104,12 +103,12 @@ lets you design your own [custom functions](custom-functions). | OR | Logical | Returns TRUE if at least one argument is TRUE. | OR(Logicalvalue1; Logicalvalue2 ...Logicalvalue30) | | TRUE | Logical | The logical value is set to TRUE. | TRUE() | | XOR | Logical | Returns true if an odd number of arguments evaluates to TRUE. | XOR(Logicalvalue1; Logicalvalue2 ...Logicalvalue30) | +| CHOOSE | Lookup and reference | Uses an index to return a value from a list of up to 30 values.| CHOOSE(Index; Value1; ...; Value30) | | COLUMNS | Lookup and reference | Returns the number of columns in the given reference. | COLUMNS(Array) | | OFFSET | Lookup and reference | Returns the value of a cell offset by a certain number of rows and columns from a given reference point. | OFFSET(Reference; Rows; Columns; Height; Width) | | ROWS | Lookup and reference | Returns the number of rows in the given reference. | ROWS(Array) | | INDEX | Lookup and reference | Returns the content of a cell, specified by row and column number, or an optional range name. | INDEX(Reference; Row; Column; Range) | | MATCH | Lookup and reference | Returns the relative position of an item in an array that matches a specified value. | MATCH(Searchcriterion; Lookuparray; Type) | -| TRANSPOSE | Matrix functions | Transposes the rows and columns of an array. | TRANSPOSE(Array) | | VLOOKUP | Lookup and reference | Searches vertically with reference to adjacent cells to the right. | VLOOKUP(Search_Criterion; Array; Index; Sort_Order) | | ABS | Math and trigonometry | Returns the absolute value of a number. | ABS(Number) | | ACOS | Math and trigonometry | Returns the inverse trigonometric cosine of a number. | ACOS(Number) | @@ -130,9 +129,6 @@ lets you design your own [custom functions](custom-functions). | LN | Math and trigonometry | Returns the natural logarithm based on the constant e of a number. | LN(Number) | | LOG | Math and trigonometry | Returns the logarithm of a number to the specified base. | LOG(Number; Base) | | LOG10 | Math and trigonometry | Returns the base-10 logarithm of a number. | LOG10(Number) | -| MMULT | Matrix functions | Calculates the array product of two arrays. | MMULT(Array; Array) | -| MEDIANPOOL | Matrix functions | Calculates a smaller range which is a median of a Window_size, in a given Range, for every Stride element. | MEDIANPOOL(Range, Window_size, Stride) | -| MAXPOOL | Matrix functions | Calculates a smaller range which is a maximum of a Window_size, in a given Range, for every Stride element. | MAXPOOL(Range, Window_size, Stride) | | MOD | Math and trigonometry | Returns the remainder when one integer is divided by another. | MOD(Dividend; Divisor) | | ODD | Math and trigonometry | Rounds a positive number up to the nearest odd integer and a negative number down to the nearest odd integer. | ODD(Number) | | PI | Math and trigonometry | Returns 3.14159265358979, the value of the mathematical constant PI to 14 decimal places. | PI() | @@ -151,6 +147,10 @@ lets you design your own [custom functions](custom-functions). | SUMSQ | Math and trigonometry | Returns the sum of the squares of the arguments | SUMSQ(Number1; Number2; ...; Number30) | | TAN | Math and trigonometry | Returns the tangent of the given angle (in radians). | TAN(Number) | | TRUNC | Math and trigonometry | Truncates a number by removing decimal places. | TRUNC(Number; Count) | +| MMULT | Matrix functions | Calculates the array product of two arrays. | MMULT(Array; Array) | +| MEDIANPOOL | Matrix functions | Calculates a smaller range which is a median of a Window_size, in a given Range, for every Stride element. | MEDIANPOOL(Range, Window_size, Stride) | +| MAXPOOL | Matrix functions | Calculates a smaller range which is a maximum of a Window_size, in a given Range, for every Stride element. | MAXPOOL(Range, Window_size, Stride) | +| TRANSPOSE | Matrix functions | Transposes the rows and columns of an array. | TRANSPOSE(Array) | | AVERAGE | Statistical | Returns the average of the arguments. | AVERAGE(Number1; Number2; ...Number30) | | AVERAGEA | Statistical | Returns the average of the arguments. | AVERAGEA(Value1; Value2; ... Value30) | | AVERAGEIF | Statistical | Returns the arithmetic mean of all cells in a range that satisfy a given condition. | AVERAGEIF(Range; Criterion [; Average_Range ]) | From db5aa53c5dbd10b396da3ac3af3fde767c92b847 Mon Sep 17 00:00:00 2001 From: izulin Date: Wed, 22 Jul 2020 14:57:58 +0200 Subject: [PATCH 24/97] . --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c79a1ab178..34b0eb47c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- 11 text functions CONCATENATE, SPLIT, LEN, TRIM, PROPER, CLEAN, REPT, RIGHT, LEFT, SEARCH, FIND. +- 9 text functions LEN, TRIM, PROPER, CLEAN, REPT, RIGHT, LEFT, SEARCH, FIND. - helper methods for keeping track of cell/range dependencies: getCellPrecedents and getCellDependents. (#441) - 4 financial functions FV, PMT, PPMT, IPMT. From ea61e03aca0679e0ebba083cf27341ebad6dfc65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Uzna=C5=84ski?= <40573492+izulin@users.noreply.github.com> Date: Wed, 22 Jul 2020 15:16:01 +0200 Subject: [PATCH 25/97] Update built-in-functions.md --- docs/guide/built-in-functions.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index 29d18ef9d6..21ea2c3e63 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -170,3 +170,12 @@ lets you design your own [custom functions](custom-functions). | CONCATENATE | Text | Combines several text strings into one string. | CONCATENATE("Text1"; ...; "Text30") | | SPLIT | Text | Divides text around a specified character or string, and puts each fragment into a separate cell in the row. | SPLIT(Text, Delimiter, [Split_by_each], [Remove_empty_text]) | | TEXT | Text | Converts a number into text according to a given format. | TEXT(Number; Format) | +| LEN | Text | Returns length of a given text. | LEN("Text") | +| TRIM | Text | Strips extra spaces from text. | TRIM("Text") | +| PROPER | Text | Capitalizes words given text string. | PROPER("Text") | +| CLEAN | Text | Returns text that has been "cleaned" of line breaks and other non-printable characters. | CLEAN("Text") | +| REPT | Text | Repeats text a given number of times. | REPT("Text"; Number) | +| RIGHT | Text | Extracts a given number of characters from the right side of a text string. | RIGHT("Text"; Number) | +| LEFT | Text | Extracts a given number of characters from the left side of a text string. | LEFT("Text"; Number) | +| SEARCH | Text | Returns the location of one text string inside another. (Allows the use of wildcards.) | SEARCH( "Text1"; "Text2"[; Number]) | +| FIND | Text | Returns the location of one text string inside another. | FIND( "Text1"; "Text2"[; Number]) | From 44a896be3c4812d3b32a40f53e074289cb0848cd Mon Sep 17 00:00:00 2001 From: izulin Date: Wed, 22 Jul 2020 15:29:21 +0200 Subject: [PATCH 26/97] trigonometry --- src/interpreter/plugin/TrigonometryPlugin.ts | 45 ++++---------------- 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/src/interpreter/plugin/TrigonometryPlugin.ts b/src/interpreter/plugin/TrigonometryPlugin.ts index 95e33228a0..99158a4efd 100644 --- a/src/interpreter/plugin/TrigonometryPlugin.ts +++ b/src/interpreter/plugin/TrigonometryPlugin.ts @@ -34,6 +34,10 @@ export class TrigonometryPlugin extends FunctionPlugin { }, 'ATAN2': { method: 'atan2', + parameters: [ + { argumentType: 'number' }, + { argumentType: 'number' }, + ], }, 'COT': { method: 'ctg', @@ -69,54 +73,23 @@ export class TrigonometryPlugin extends FunctionPlugin { } public cos(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, (coercedArg) => { - return Math.cos(coercedArg) - }) + return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, Math.cos) } public sin(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, (coercedArg) => { - return Math.sin(coercedArg) - }) + return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, Math.sin) } public tan(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, (coercedArg) => { - return Math.tan(coercedArg) - }) + return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, Math.tan) } public atan(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, (coercedArg) => { - return Math.atan(coercedArg) - }) + return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, Math.atan) } public atan2(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length !== 2) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - const arg1 = this.evaluateAst(ast.args[0], formulaAddress) - if (arg1 instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - const arg2 = this.evaluateAst(ast.args[1], formulaAddress) - if (arg2 instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - const coercedArg1 = this.coerceScalarToNumberOrError(arg1) - if (coercedArg1 instanceof CellError) { - return coercedArg1 - } - const coercedArg2 = this.coerceScalarToNumberOrError(arg2) - if (coercedArg2 instanceof CellError) { - return coercedArg2 - } - return Math.atan2(coercedArg1, coercedArg2) + return this.runFunctionWithDefaults(ast.args, formulaAddress, TrigonometryPlugin.implementedFunctions.ATAN2.parameters, Math.atan2) } public ctg(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { From 4eb442cfbf72b7a98de095647b201ed0c2efc237 Mon Sep 17 00:00:00 2001 From: izulin Date: Wed, 22 Jul 2020 16:01:32 +0200 Subject: [PATCH 27/97] . --- src/interpreter/plugin/FunctionPlugin.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 157b629002..8bc66eb3fe 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -9,7 +9,7 @@ import {ColumnSearchStrategy} from '../../ColumnSearch/ColumnSearchStrategy' import {Config} from '../../Config' import {DependencyGraph} from '../../DependencyGraph' import {Ast, AstNodeType, ProcedureAst} from '../../parser' -import {coerceScalarToString} from '../ArithmeticHelper' +import {coerceScalarToBoolean, coerceScalarToString} from '../ArithmeticHelper' import {Interpreter} from '../Interpreter' import {InterpreterValue, SimpleRangeValue} from '../InterpreterValue' import {Maybe} from '../../Maybe' @@ -31,7 +31,7 @@ export interface FunctionPluginDefinition { implementedFunctions: ImplementedFunctions, } -export type ArgumentTypes = 'string' | 'number' +export type ArgumentTypes = 'string' | 'number' | 'boolean' export interface FunctionArgumentDefinition { argumentType: string, @@ -155,6 +155,8 @@ export abstract class FunctionPlugin { return this.coerceScalarToNumberOrError(arg) case 'string': return coerceScalarToString(arg) + case 'boolean': + return coerceScalarToBoolean(arg) } } From 444f1de82a3dbe920e5178db9e0607b4d31d2c3b Mon Sep 17 00:00:00 2001 From: izulin Date: Wed, 22 Jul 2020 23:56:42 +0200 Subject: [PATCH 28/97] booleans --- src/interpreter/ArithmeticHelper.ts | 4 +- src/interpreter/plugin/BooleanPlugin.ts | 79 +++++++++--------------- src/interpreter/plugin/FunctionPlugin.ts | 6 +- test/interpreter/coercions.spec.ts | 8 +-- 4 files changed, 37 insertions(+), 60 deletions(-) diff --git a/src/interpreter/ArithmeticHelper.ts b/src/interpreter/ArithmeticHelper.ts index ad26ce40ab..970ad07801 100644 --- a/src/interpreter/ArithmeticHelper.ts +++ b/src/interpreter/ArithmeticHelper.ts @@ -289,7 +289,7 @@ export function coerceEmptyToValue(arg: InternalNoErrorCellValue): InternalNoErr * * @param arg */ -export function coerceScalarToBoolean(arg: InternalScalarValue): boolean | CellError | null { +export function coerceScalarToBoolean(arg: InternalScalarValue): boolean | CellError | undefined { if (arg instanceof SimpleRangeValue) { return new CellError(ErrorType.VALUE) } else if (arg instanceof CellError || typeof arg === 'boolean') { @@ -307,7 +307,7 @@ export function coerceScalarToBoolean(arg: InternalScalarValue): boolean | CellE } else if (argUppered === '') { return false } else { - return null + return undefined } } } diff --git a/src/interpreter/plugin/BooleanPlugin.ts b/src/interpreter/plugin/BooleanPlugin.ts index a487d31416..830ca02414 100644 --- a/src/interpreter/plugin/BooleanPlugin.ts +++ b/src/interpreter/plugin/BooleanPlugin.ts @@ -4,6 +4,7 @@ */ import {CellError, ErrorType, InternalNoErrorCellValue, InternalScalarValue, SimpleCellAddress} from '../../Cell' +import {Maybe} from '../../Maybe' import {AstNodeType, ProcedureAst} from '../../parser' import {coerceScalarToBoolean} from '../ArithmeticHelper' import {InterpreterValue, SimpleRangeValue} from '../InterpreterValue' @@ -15,7 +16,8 @@ import {FunctionPlugin} from './FunctionPlugin' export class BooleanPlugin extends FunctionPlugin { public static implementedFunctions = { 'TRUE': { - method: 'literalTrue' + method: 'literalTrue', + parameters: [], }, 'FALSE': { method: 'literalFalse' @@ -24,10 +26,16 @@ export class BooleanPlugin extends FunctionPlugin { method: 'conditionalIf' }, 'AND': { - method: 'and' + method: 'and', + parameters: [ + { argumentType: 'boolean' }, + ], }, 'OR': { - method: 'or' + method: 'or', + parameters: [ + { argumentType: 'boolean' }, + ], }, 'XOR': { method: 'xor' @@ -57,13 +65,8 @@ export class BooleanPlugin extends FunctionPlugin { * @param ast * @param formulaAddress */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars public literalTrue(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length > 0) { - return new CellError(ErrorType.NA) - } else { - return true - } + return this.runFunctionWithDefaults(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.TRUE.parameters, () => true) } /** @@ -127,29 +130,15 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public and(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length < 1) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - let result: InternalScalarValue = true - let anyReasonableValue = false - for (const scalarValue of this.iterateOverScalarValues(ast.args, formulaAddress)) { - const coercedValue = coerceScalarToBoolean(scalarValue) - if (coercedValue instanceof CellError) { - return coercedValue - } else if (coercedValue !== null) { - result = result && coercedValue - anyReasonableValue = true + return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.AND.parameters, 1, (...args) => { + if(args.some((arg:Maybe) => arg===false)) { + return false + } else if(args.some((arg:Maybe) => arg!==undefined)) { + return true + } else { + return new CellError(ErrorType.VALUE) } - } - if (anyReasonableValue) { - return result - } else { - return new CellError(ErrorType.VALUE) - } + }) } /** @@ -161,27 +150,15 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public or(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length < 1) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - let result: InternalScalarValue | null = null - for (const scalarValue of this.iterateOverScalarValues(ast.args, formulaAddress)) { - const coercedValue = coerceScalarToBoolean(scalarValue) - if (coercedValue instanceof CellError) { - return coercedValue - } else if (coercedValue !== null) { - result = result || coercedValue + return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.OR.parameters, 1, (...args) => { + if(args.some((arg:Maybe) => arg===true)) { + return true + } else if(args.some((arg:Maybe) => arg!==undefined)) { + return false + } else { + return new CellError(ErrorType.VALUE) } - } - if (result === null) { - return new CellError(ErrorType.VALUE) - } else { - return result - } + }) } public not(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 8bc66eb3fe..3f6651274f 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -149,7 +149,7 @@ export abstract class FunctionPlugin { public coerceScalarToNumberOrError = (arg: InternalScalarValue): number | CellError => this.interpreter.arithmeticHelper.coerceScalarToNumberOrError(arg) - public coerceToType(arg: InternalScalarValue, coercedType: ArgumentTypes): InternalScalarValue { + public coerceToType(arg: InternalScalarValue, coercedType: ArgumentTypes): Maybe { switch(coercedType) { case 'number': return this.coerceScalarToNumberOrError(arg) @@ -170,7 +170,7 @@ export abstract class FunctionPlugin { return new CellError(ErrorType.NA) } - const coercedArguments: InternalScalarValue[] = [] + const coercedArguments: Maybe[] = [] for (let i = 0; i < argumentDefinitions.length; ++i) { const arg = this.evaluateArgOrDefault(formulaAddress, args[i], argumentDefinitions[i].defaultValue) @@ -195,7 +195,7 @@ export abstract class FunctionPlugin { fn: (...arg: any) => InternalScalarValue ) => { const scalarValues: InternalScalarValue[] = [...this.iterateOverScalarValues(args, formulaAddress)] - const coercedArguments: InternalScalarValue[] = [] + const coercedArguments: Maybe[] = [] let j = 0 let i = 0 diff --git a/test/interpreter/coercions.spec.ts b/test/interpreter/coercions.spec.ts index 88326b49d3..1d391b79df 100644 --- a/test/interpreter/coercions.spec.ts +++ b/test/interpreter/coercions.spec.ts @@ -61,10 +61,10 @@ describe('#coerceScalarToBoolean', () => { expect(coerceScalarToBoolean('FALSE')).toBe(false) expect(coerceScalarToBoolean('true')).toBe(true) expect(coerceScalarToBoolean('TRUE')).toBe(true) - expect(coerceScalarToBoolean(' ')).toBe(null) - expect(coerceScalarToBoolean(' true')).toBe(null) - expect(coerceScalarToBoolean('true ')).toBe(null) - expect(coerceScalarToBoolean('prawda')).toBe(null) + expect(coerceScalarToBoolean(' ')).toBe(undefined) + expect(coerceScalarToBoolean(' true')).toBe(undefined) + expect(coerceScalarToBoolean('true ')).toBe(undefined) + expect(coerceScalarToBoolean('prawda')).toBe(undefined) expect(coerceScalarToBoolean('')).toBe(false) expect(coerceScalarToBoolean(EmptyValue)).toBe(false) From a1a0066ea7bbad23fb1bd796985e3b29f1beda76 Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 23 Jul 2020 01:23:51 +0200 Subject: [PATCH 29/97] boolean functions --- src/interpreter/plugin/BooleanPlugin.ts | 140 +++++++++-------------- src/interpreter/plugin/FunctionPlugin.ts | 10 +- test/interpreter/function-if.spec.ts | 6 + 3 files changed, 65 insertions(+), 91 deletions(-) diff --git a/src/interpreter/plugin/BooleanPlugin.ts b/src/interpreter/plugin/BooleanPlugin.ts index 830ca02414..ff007cc8ba 100644 --- a/src/interpreter/plugin/BooleanPlugin.ts +++ b/src/interpreter/plugin/BooleanPlugin.ts @@ -20,10 +20,16 @@ export class BooleanPlugin extends FunctionPlugin { parameters: [], }, 'FALSE': { - method: 'literalFalse' + method: 'literalFalse', + parameters: [], }, 'IF': { - method: 'conditionalIf' + method: 'conditionalIf', + parameters: [ + { argumentType: 'boolean' }, + { argumentType: 'scalar' }, + { argumentType: 'scalar', defaultValue: false }, + ], }, 'AND': { method: 'and', @@ -41,16 +47,27 @@ export class BooleanPlugin extends FunctionPlugin { method: 'xor' }, 'NOT': { - method: 'not' + method: 'not', + parameters: [ + { argumentType: 'boolean' }, + ], }, 'SWITCH': { method: 'switch' }, 'IFERROR': { - method: 'iferror' + method: 'iferror', + parameters: [ + { argumentType: 'scalar' }, + { argumentType: 'scalar' }, + ], }, 'IFNA': { - method: 'ifna' + method: 'ifna', + parameters: [ + { argumentType: 'scalar' }, + { argumentType: 'scalar' }, + ], }, 'CHOOSE': { method: 'choose' @@ -77,13 +94,8 @@ export class BooleanPlugin extends FunctionPlugin { * @param ast * @param formulaAddress */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars public literalFalse(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length > 0) { - return new CellError(ErrorType.NA) - } else { - return false - } + return this.runFunctionWithDefaults(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.FALSE.parameters, () => false) } /** @@ -95,30 +107,13 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public conditionalIf(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InterpreterValue { - if (ast.args.length > 3 || ast.args.length < 2) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - const conditionValue = this.evaluateAst(ast.args[0], formulaAddress) - if (conditionValue instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - const condition = coerceScalarToBoolean(conditionValue) - if (condition === true) { - return this.evaluateAst(ast.args[1], formulaAddress) - } else if (condition === false) { - if (ast.args[2] !== undefined) { - return this.evaluateAst(ast.args[2], formulaAddress) + return this.runFunctionWithDefaults(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.IF.parameters, (condition, arg2, arg3) => { + if(condition===undefined) { + return new CellError(ErrorType.VALUE) } else { - return false + return condition ? arg2 : arg3 } - } else if (condition instanceof CellError) { - return condition - } else { - return new CellError(ErrorType.VALUE) - } + }) } /** @@ -131,9 +126,9 @@ export class BooleanPlugin extends FunctionPlugin { */ public and(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.AND.parameters, 1, (...args) => { - if(args.some((arg:Maybe) => arg===false)) { + if(args.some((arg: Maybe) => arg===false)) { return false - } else if(args.some((arg:Maybe) => arg!==undefined)) { + } else if(args.some((arg: Maybe) => arg!==undefined)) { return true } else { return new CellError(ErrorType.VALUE) @@ -151,9 +146,9 @@ export class BooleanPlugin extends FunctionPlugin { */ public or(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.OR.parameters, 1, (...args) => { - if(args.some((arg:Maybe) => arg===true)) { + if(args.some((arg: Maybe) => arg===true)) { return true - } else if(args.some((arg:Maybe) => arg!==undefined)) { + } else if(args.some((arg: Maybe) => arg!==undefined)) { return false } else { return new CellError(ErrorType.VALUE) @@ -162,24 +157,13 @@ export class BooleanPlugin extends FunctionPlugin { } public not(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length !== 1) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - const argValue = this.evaluateAst(ast.args[0], formulaAddress) - if (argValue instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } else { - const coercedValue = coerceScalarToBoolean(argValue) - if (coercedValue instanceof CellError) { - return coercedValue + return this.runFunctionWithDefaults(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.NOT.parameters, (arg) => { + if(arg===undefined) { + return new CellError(ErrorType.VALUE) } else { - return !coercedValue + return !arg } - } + }) } public xor(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { @@ -247,45 +231,23 @@ export class BooleanPlugin extends FunctionPlugin { } public iferror(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length !== 2) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - const left: InterpreterValue = this.evaluateAst(ast.args[0], formulaAddress) - const right: InterpreterValue = this.evaluateAst(ast.args[1], formulaAddress) - - if (left instanceof SimpleRangeValue || right instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - if (left instanceof CellError) { - return right - } else { - return left - } + return this.runFunctionWithDefaults(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.IFERROR.parameters, (arg1: InternalScalarValue, arg2: InternalScalarValue) => { + if(arg1 instanceof CellError) { + return arg2 + } else { + return arg1 + } + }) } public ifna(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length !== 2) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - const left: InterpreterValue = this.evaluateAst(ast.args[0], formulaAddress) - const right: InterpreterValue = this.evaluateAst(ast.args[1], formulaAddress) - - if (left instanceof SimpleRangeValue || right instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - if (left instanceof CellError && left.type === ErrorType.NA) { - return right - } else { - return left - } + return this.runFunctionWithDefaults(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.IFNA.parameters, (arg1: InternalScalarValue, arg2: InternalScalarValue) => { + if(arg1 instanceof CellError && arg1.type === ErrorType.NA) { + return arg2 + } else { + return arg1 + } + }) } public choose(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 3f6651274f..cb1d63e216 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -31,7 +31,7 @@ export interface FunctionPluginDefinition { implementedFunctions: ImplementedFunctions, } -export type ArgumentTypes = 'string' | 'number' | 'boolean' +export type ArgumentTypes = 'string' | 'number' | 'boolean' | 'scalar' export interface FunctionArgumentDefinition { argumentType: string, @@ -157,6 +157,8 @@ export abstract class FunctionPlugin { return coerceScalarToString(arg) case 'boolean': return coerceScalarToBoolean(arg) + case 'scalar': + return arg } } @@ -173,12 +175,15 @@ export abstract class FunctionPlugin { const coercedArguments: Maybe[] = [] for (let i = 0; i < argumentDefinitions.length; ++i) { + if(args[i] === undefined && argumentDefinitions[i].defaultValue === undefined) { + return new CellError(ErrorType.NA) + } const arg = this.evaluateArgOrDefault(formulaAddress, args[i], argumentDefinitions[i].defaultValue) if (arg instanceof SimpleRangeValue) { return new CellError(ErrorType.VALUE) } const coercedArg = this.coerceToType(arg, argumentDefinitions[i].argumentType as ArgumentTypes) - if (coercedArg instanceof CellError) { + if (coercedArg instanceof CellError && argumentDefinitions[i].argumentType !== 'scalar') { return coercedArg } coercedArguments.push(coercedArg) @@ -199,6 +204,7 @@ export abstract class FunctionPlugin { let j = 0 let i = 0 + //eslint-disable-next-line no-constant-condition while(true) { const arg = scalarValues[i] ?? argumentDefinitions[j].defaultValue ?? new CellError(ErrorType.NA) const coercedArg = this.coerceToType(arg, argumentDefinitions[j].argumentType as ArgumentTypes) diff --git a/test/interpreter/function-if.spec.ts b/test/interpreter/function-if.spec.ts index 07dc3c42a9..16a338c36a 100644 --- a/test/interpreter/function-if.spec.ts +++ b/test/interpreter/function-if.spec.ts @@ -44,6 +44,12 @@ describe('Function IF', () => { expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.DIV_BY_ZERO)) }) + it('passes errors', () => { + const engine = HyperFormula.buildFromArray([['=IF(TRUE(), 4/0, "no")']]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.DIV_BY_ZERO)) + }) + it('when condition is number', () => { const engine = HyperFormula.buildFromArray([['=IF(1, "yes", "no")']]) From 381a3096f6380611883762c7bf9a45708c79fde8 Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 23 Jul 2020 10:40:05 +0200 Subject: [PATCH 30/97] refactor --- src/interpreter/plugin/BooleanPlugin.ts | 4 ++-- src/interpreter/plugin/FunctionPlugin.ts | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/interpreter/plugin/BooleanPlugin.ts b/src/interpreter/plugin/BooleanPlugin.ts index ff007cc8ba..ceb8f63f2b 100644 --- a/src/interpreter/plugin/BooleanPlugin.ts +++ b/src/interpreter/plugin/BooleanPlugin.ts @@ -273,9 +273,9 @@ export class BooleanPlugin extends FunctionPlugin { return vals[0] } - const selector = this.interpreter.arithmeticHelper.coerceToMaybeNumber(vals[0]) + const selector = this.interpreter.arithmeticHelper.coerceScalarToNumberOrError(vals[0]) - if (selector === undefined || selector != Math.round(selector) || selector < 1 || selector >= n) { + if (selector instanceof CellError || selector != Math.round(selector) || selector < 1 || selector >= n) { return new CellError(ErrorType.NUM) } return vals[selector] diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index cb1d63e216..f5b004d1b9 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -206,7 +206,10 @@ export abstract class FunctionPlugin { let i = 0 //eslint-disable-next-line no-constant-condition while(true) { - const arg = scalarValues[i] ?? argumentDefinitions[j].defaultValue ?? new CellError(ErrorType.NA) + const arg = scalarValues[i] ?? argumentDefinitions[j].defaultValue + if(arg === undefined) { + return new CellError(ErrorType.NA) + } const coercedArg = this.coerceToType(arg, argumentDefinitions[j].argumentType as ArgumentTypes) if (coercedArg instanceof CellError) { return coercedArg From 2a61e737a96206d5f2bf4a62f4523b43f363a204 Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 23 Jul 2020 12:12:02 +0200 Subject: [PATCH 31/97] xor --- src/interpreter/plugin/BooleanPlugin.ts | 129 ++++++++--------------- src/interpreter/plugin/FunctionPlugin.ts | 43 +++++++- test/interpreter/function-if.spec.ts | 6 ++ 3 files changed, 91 insertions(+), 87 deletions(-) diff --git a/src/interpreter/plugin/BooleanPlugin.ts b/src/interpreter/plugin/BooleanPlugin.ts index ceb8f63f2b..ac4d499b81 100644 --- a/src/interpreter/plugin/BooleanPlugin.ts +++ b/src/interpreter/plugin/BooleanPlugin.ts @@ -44,7 +44,10 @@ export class BooleanPlugin extends FunctionPlugin { ], }, 'XOR': { - method: 'xor' + method: 'xor', + parameters: [ + { argumentType: 'boolean' }, + ], }, 'NOT': { method: 'not', @@ -53,7 +56,12 @@ export class BooleanPlugin extends FunctionPlugin { ], }, 'SWITCH': { - method: 'switch' + method: 'switch', + parameters: [ + { argumentType: 'noerror' }, + { argumentType: 'scalar' }, + { argumentType: 'scalar' }, + ], }, 'IFERROR': { method: 'iferror', @@ -70,7 +78,11 @@ export class BooleanPlugin extends FunctionPlugin { ], }, 'CHOOSE': { - method: 'choose' + method: 'choose', + parameters: [ + { argumentType: 'number' }, + { argumentType: 'scalar' }, + ], }, } @@ -167,67 +179,36 @@ export class BooleanPlugin extends FunctionPlugin { } public xor(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length < 1) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - let truesCount = 0 - let anyFalseValue = false - for (const scalarValue of this.iterateOverScalarValues(ast.args, formulaAddress)) { - const coercedValue = coerceScalarToBoolean(scalarValue) - if (coercedValue instanceof CellError) { - return coercedValue - } else if (coercedValue === true) { - truesCount++ - } else if (coercedValue === false) { - anyFalseValue = true + return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.XOR.parameters, 1, (...args) => { + if(args.some((arg: Maybe) => arg!==undefined)) { + let cnt = 0 + args.forEach((arg: Maybe) => {if(arg===true){cnt++}}) + // @ts-ignore + return (cnt%2) === 1 + } else { + return new CellError(ErrorType.VALUE) } - } - if (anyFalseValue || truesCount > 0) { - return (truesCount % 2 === 1) - } else { - return new CellError(ErrorType.VALUE) - } + }) } public switch(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length < 3) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - const vals: InternalScalarValue[] = [] - for (const arg of ast.args) { - const val: InterpreterValue = this.evaluateAst(arg, formulaAddress) - if (val instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - vals.push(val) - } - const n = vals.length - if (vals[0] instanceof CellError) { - return vals[0] - } - - let i = 1 - for (; i + 1 < n; i += 2) { - if (vals[i] instanceof CellError) { - continue + return this.runFunctionWithRepeatedArgNoRanges(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.SWITCH.parameters, 1, (selector, ...args) => { + const n = args.length + let i = 0 + for (; i + 1 < n; i += 2) { + if (args[i] instanceof CellError) { + continue + } + if (this.interpreter.arithmeticHelper.compare(selector, args[i] as InternalNoErrorCellValue) === 0) { + return args[i+1] + } } - if (this.interpreter.arithmeticHelper.compare(vals[0], vals[i] as InternalNoErrorCellValue) === 0) { - return vals[i + 1] + if (i < n) { + return args[i] + } else { + return new CellError(ErrorType.NA) } - } - if (i < n) { - return vals[i] - } else { - return new CellError(ErrorType.NA) - } + }) } public iferror(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { @@ -251,33 +232,11 @@ export class BooleanPlugin extends FunctionPlugin { } public choose(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length < 2) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - const vals: InternalScalarValue[] = [] - for (const arg of ast.args) { - const val: InterpreterValue = this.evaluateAst(arg, formulaAddress) - if (val instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) + return this.runFunctionWithRepeatedArgNoRanges(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.CHOOSE.parameters, 1,(selector, ...args) => { + if(selector !== Math.round(selector) || selector<1 || selector > args.length) { + return new CellError(ErrorType.NUM) } - vals.push(val) - } - - const n = vals.length - - if (vals[0] instanceof CellError) { - return vals[0] - } - - const selector = this.interpreter.arithmeticHelper.coerceScalarToNumberOrError(vals[0]) - - if (selector instanceof CellError || selector != Math.round(selector) || selector < 1 || selector >= n) { - return new CellError(ErrorType.NUM) - } - return vals[selector] + return args[selector-1] + }) } } diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index f5b004d1b9..a5750e4569 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -31,7 +31,7 @@ export interface FunctionPluginDefinition { implementedFunctions: ImplementedFunctions, } -export type ArgumentTypes = 'string' | 'number' | 'boolean' | 'scalar' +export type ArgumentTypes = 'string' | 'number' | 'boolean' | 'scalar' | 'noerror' export interface FunctionArgumentDefinition { argumentType: string, @@ -159,6 +159,8 @@ export abstract class FunctionPlugin { return coerceScalarToBoolean(arg) case 'scalar': return arg + case 'noerror': + return arg } } @@ -211,7 +213,7 @@ export abstract class FunctionPlugin { return new CellError(ErrorType.NA) } const coercedArg = this.coerceToType(arg, argumentDefinitions[j].argumentType as ArgumentTypes) - if (coercedArg instanceof CellError) { + if (coercedArg instanceof CellError && argumentDefinitions[j].argumentType !== 'scalar') { return coercedArg } coercedArguments.push(coercedArg) @@ -228,6 +230,43 @@ export abstract class FunctionPlugin { return fn(...coercedArguments) } + protected runFunctionWithRepeatedArgNoRanges = ( + args: Ast[], + formulaAddress: SimpleCellAddress, + argumentDefinitions: FunctionArgumentDefinition[], + repeatedArgs: number, + fn: (...arg: any) => InternalScalarValue + ) => { + const coercedArguments: Maybe[] = [] + let j = 0 + let i = 0 + //eslint-disable-next-line no-constant-condition + while(true) { + if(args[i] === undefined && argumentDefinitions[j].defaultValue === undefined) { + return new CellError(ErrorType.NA) + } + const arg = this.evaluateArgOrDefault(formulaAddress, args[i], argumentDefinitions[j].defaultValue) + if (arg instanceof SimpleRangeValue) { + return new CellError(ErrorType.VALUE) + } + const coercedArg = this.coerceToType(arg, argumentDefinitions[j].argumentType as ArgumentTypes) + if (coercedArg instanceof CellError && argumentDefinitions[j].argumentType !== 'scalar') { + return coercedArg + } + coercedArguments.push(coercedArg) + j++ + i++ + if(i >= args.length && j === argumentDefinitions.length) { + break + } + if(j===argumentDefinitions.length) { + j -= repeatedArgs + } + } + + return fn(...coercedArguments) + } + protected evaluateArgOrDefault = (formulaAddress: SimpleCellAddress, argAst?: Ast, defaultValue?: InternalScalarValue): InterpreterValue => { if (argAst !== undefined) { return this.evaluateAst(argAst, formulaAddress) diff --git a/test/interpreter/function-if.spec.ts b/test/interpreter/function-if.spec.ts index 16a338c36a..98c7cd1aa1 100644 --- a/test/interpreter/function-if.spec.ts +++ b/test/interpreter/function-if.spec.ts @@ -50,6 +50,12 @@ describe('Function IF', () => { expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.DIV_BY_ZERO)) }) + it('passes correct value when other arg is an error', () => { + const engine = HyperFormula.buildFromArray([['=IF(FALSE(), 4/0, "no")']]) + + expect(engine.getCellValue(adr('A1'))).toEqual("no") + }) + it('when condition is number', () => { const engine = HyperFormula.buildFromArray([['=IF(1, "yes", "no")']]) From 714fea1e54580adc359cd46ee8fef15e8f9c6ff9 Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 23 Jul 2020 16:57:36 +0200 Subject: [PATCH 32/97] . --- src/interpreter/plugin/BooleanPlugin.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/interpreter/plugin/BooleanPlugin.ts b/src/interpreter/plugin/BooleanPlugin.ts index ac4d499b81..276371ee20 100644 --- a/src/interpreter/plugin/BooleanPlugin.ts +++ b/src/interpreter/plugin/BooleanPlugin.ts @@ -182,8 +182,11 @@ export class BooleanPlugin extends FunctionPlugin { return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.XOR.parameters, 1, (...args) => { if(args.some((arg: Maybe) => arg!==undefined)) { let cnt = 0 - args.forEach((arg: Maybe) => {if(arg===true){cnt++}}) - // @ts-ignore + args.forEach((arg: Maybe) => { + if( arg===true ) { + cnt++ + } + }) return (cnt%2) === 1 } else { return new CellError(ErrorType.VALUE) From a193f36120c5f5031a249dda17110c30a2e7ffb7 Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 23 Jul 2020 17:15:56 +0200 Subject: [PATCH 33/97] . --- src/interpreter/plugin/BooleanPlugin.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/interpreter/plugin/BooleanPlugin.ts b/src/interpreter/plugin/BooleanPlugin.ts index 276371ee20..81488c9031 100644 --- a/src/interpreter/plugin/BooleanPlugin.ts +++ b/src/interpreter/plugin/BooleanPlugin.ts @@ -5,9 +5,8 @@ import {CellError, ErrorType, InternalNoErrorCellValue, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {Maybe} from '../../Maybe' -import {AstNodeType, ProcedureAst} from '../../parser' -import {coerceScalarToBoolean} from '../ArithmeticHelper' -import {InterpreterValue, SimpleRangeValue} from '../InterpreterValue' +import {ProcedureAst} from '../../parser' +import {InterpreterValue} from '../InterpreterValue' import {FunctionPlugin} from './FunctionPlugin' /** From 114bf133ba9947dba86c74ec1464cd12b8d1063a Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 23 Jul 2020 17:58:23 +0200 Subject: [PATCH 34/97] merge --- src/interpreter/plugin/FunctionPlugin.ts | 38 ------- src/interpreter/plugin/MedianPlugin.ts | 40 +++----- src/interpreter/plugin/TextPlugin.ts | 120 ----------------------- 3 files changed, 14 insertions(+), 184 deletions(-) diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 959a8113ba..a5750e4569 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -31,15 +31,10 @@ export interface FunctionPluginDefinition { implementedFunctions: ImplementedFunctions, } -<<<<<<< HEAD export type ArgumentTypes = 'string' | 'number' | 'boolean' | 'scalar' | 'noerror' export interface FunctionArgumentDefinition { argumentType: string, -======= -export interface FunctionArgumentDefinition { - typeCoercionFunction: (arg: InternalScalarValue) => InternalScalarValue, ->>>>>>> develop defaultValue?: InternalScalarValue, } @@ -95,19 +90,11 @@ export abstract class FunctionPlugin { } protected templateWithOneCoercedToNumberArgument(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (arg: number) => InternalScalarValue): InternalScalarValue { -<<<<<<< HEAD return this.runFunctionWithDefaults(ast.args, formulaAddress, [{ argumentType: 'number'}], fn) } protected templateWithOneCoercedToStringArgument(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (arg: string) => InternalScalarValue): InternalScalarValue { return this.runFunctionWithDefaults(ast.args, formulaAddress, [{ argumentType: 'string' }], fn) -======= - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [{ typeCoercionFunction: this.coerceScalarToNumberOrError }], fn) - } - - protected templateWithOneCoercedToStringArgument(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (arg: string) => InternalScalarValue): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [{ typeCoercionFunction: coerceScalarToString }], fn) ->>>>>>> develop } protected validateTwoNumericArguments(ast: ProcedureAst, formulaAddress: SimpleCellAddress): [number, number] | CellError { @@ -160,7 +147,6 @@ export abstract class FunctionPlugin { return value } -<<<<<<< HEAD public coerceScalarToNumberOrError = (arg: InternalScalarValue): number | CellError => this.interpreter.arithmeticHelper.coerceScalarToNumberOrError(arg) public coerceToType(arg: InternalScalarValue, coercedType: ArgumentTypes): Maybe { @@ -179,11 +165,6 @@ export abstract class FunctionPlugin { } protected runFunctionWithDefaults = ( -======= - protected coerceScalarToNumberOrError = (arg: InternalScalarValue): number | CellError => this.interpreter.arithmeticHelper.coerceScalarToNumberOrError(arg) - - protected coerceArgumentsWithDefaults = ( ->>>>>>> develop args: Ast[], formulaAddress: SimpleCellAddress, argumentDefinitions: FunctionArgumentDefinition[], @@ -192,7 +173,6 @@ export abstract class FunctionPlugin { if (args.length > argumentDefinitions.length) { return new CellError(ErrorType.NA) } -<<<<<<< HEAD const coercedArguments: Maybe[] = [] @@ -282,20 +262,6 @@ export abstract class FunctionPlugin { if(j===argumentDefinitions.length) { j -= repeatedArgs } -======= - - const coercedArguments: InternalScalarValue[] = [] - for (let i = 0; i < argumentDefinitions.length; ++i) { - const arg = this.evaluateArgOrDefault(formulaAddress, args[i], argumentDefinitions[i].defaultValue) - if (arg instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - const coercedArg = argumentDefinitions[i].typeCoercionFunction(arg) - if (coercedArg instanceof CellError) { - return coercedArg - } - coercedArguments.push(coercedArg) ->>>>>>> develop } return fn(...coercedArguments) @@ -305,10 +271,6 @@ export abstract class FunctionPlugin { if (argAst !== undefined) { return this.evaluateAst(argAst, formulaAddress) } -<<<<<<< HEAD return defaultValue ?? new CellError(ErrorType.NA) -======= - return defaultValue || new CellError(ErrorType.NA) ->>>>>>> develop } } diff --git a/src/interpreter/plugin/MedianPlugin.ts b/src/interpreter/plugin/MedianPlugin.ts index 7add71c882..e61194cc62 100644 --- a/src/interpreter/plugin/MedianPlugin.ts +++ b/src/interpreter/plugin/MedianPlugin.ts @@ -15,6 +15,9 @@ export class MedianPlugin extends FunctionPlugin { public static implementedFunctions = { 'MEDIAN': { method: 'median', + parameters: [ + { argumentType: 'noerror' }, + ], }, } @@ -27,32 +30,17 @@ export class MedianPlugin extends FunctionPlugin { * @param formulaAddress */ public median(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length === 0) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - const values: number[] = [] - for (const scalarValue of this.iterateOverScalarValues(ast.args, formulaAddress)) { - if (scalarValue instanceof CellError) { - return scalarValue - } else if (typeof scalarValue === 'number') { - values.push(scalarValue) + return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, MedianPlugin.implementedFunctions.MEDIAN.parameters, 1, (...args) => { + const values: number[] = args.filter((val: InternalScalarValue) => (typeof val === 'number')) + if (values.length === 0) { + return new CellError(ErrorType.NUM) } - } - - if (values.length === 0) { - return new CellError(ErrorType.NUM) - } - - values.sort((a, b) => (a - b)) - - if (values.length % 2 === 0) { - return (values[(values.length / 2) - 1] + values[values.length / 2]) / 2 - } else { - return values[Math.floor(values.length / 2)] - } + values.sort((a, b) => (a - b)) + if (values.length % 2 === 0) { + return (values[(values.length / 2) - 1] + values[values.length / 2]) / 2 + } else { + return values[Math.floor(values.length / 2)] + } + }) } } diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index 0735bb2000..1f85b6c1b1 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -39,7 +39,6 @@ export class TextPlugin extends FunctionPlugin { method: 'clean' }, 'REPT': { -<<<<<<< HEAD method: 'rept', parameters: [ { argumentType: 'string' }, @@ -75,21 +74,6 @@ export class TextPlugin extends FunctionPlugin { { argumentType: 'string' }, { argumentType: 'number', defaultValue: 1 }, ], -======= - method: 'rept' - }, - 'RIGHT': { - method: 'right' - }, - 'LEFT': { - method: 'left' - }, - 'SEARCH': { - method: 'search' - }, - 'FIND': { - method: 'find' ->>>>>>> develop } } @@ -213,108 +197,4 @@ export class TextPlugin extends FunctionPlugin { return index > 0 ? index : new CellError(ErrorType.VALUE) }) } - - public len(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToStringArgument(ast, formulaAddress, (arg: string) => { - return arg.length - }) - } - - public trim(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToStringArgument(ast, formulaAddress, (arg: string) => { - return arg.replace(/^ +| +$/g, '').replace(/ +/g, ' ') - }) - } - - public proper(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToStringArgument(ast, formulaAddress, (arg: string) => { - return arg.replace(/\w\S*/g, word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase()) - }) - } - - public clean(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToStringArgument(ast, formulaAddress, (arg: string) => { - // eslint-disable-next-line no-control-regex - return arg.replace(/[\u0000-\u001F]/g, '') - }) - } - - public rept(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [ - { typeCoercionFunction: coerceScalarToString }, - { typeCoercionFunction: this.coerceScalarToNumberOrError }, - ], (text: string, count: number) => { - if (count < 0) { - return new CellError(ErrorType.VALUE) - } - return text.repeat(count) - }) - } - - public right(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [ - { typeCoercionFunction: coerceScalarToString }, - { typeCoercionFunction: this.coerceScalarToNumberOrError, defaultValue: 1 }, - ], (text: string, length: number) => { - if (length < 0) { - return new CellError(ErrorType.VALUE) - } else if (length === 0) { - return '' - } - return text.slice(-length) - }) - } - - public left(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [ - { typeCoercionFunction: coerceScalarToString }, - { typeCoercionFunction: this.coerceScalarToNumberOrError, defaultValue: 1 }, - ], (text: string, length: number) => { - if (length < 0) { - return new CellError(ErrorType.VALUE) - } - return text.slice(0, length) - }) - } - - public search(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [ - { typeCoercionFunction: coerceScalarToString }, - { typeCoercionFunction: coerceScalarToString }, - { typeCoercionFunction: this.coerceScalarToNumberOrError, defaultValue: 1 }, - ], (pattern, text: string, startIndex: number) => { - if (startIndex < 1 || startIndex > text.length) { - return new CellError(ErrorType.VALUE) - } - - const normalizedText = text.substr(startIndex - 1).toLowerCase() - - let index: number - if (this.interpreter.arithmeticHelper.requiresRegex(pattern)) { - index = this.interpreter.arithmeticHelper.searchString(pattern, normalizedText) - } else { - index = normalizedText.indexOf(pattern.toLowerCase()) - } - - index = index + startIndex - return index > 0 ? index : new CellError(ErrorType.VALUE) - }) - } - - public find(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.coerceArgumentsWithDefaults(ast.args, formulaAddress, [ - { typeCoercionFunction: coerceScalarToString }, - { typeCoercionFunction: coerceScalarToString }, - { typeCoercionFunction: this.coerceScalarToNumberOrError, defaultValue: 1 }, - ], (pattern, text: string, startIndex: number) => { - if (startIndex < 1 || startIndex > text.length) { - return new CellError(ErrorType.VALUE) - } - - const shiftedText = text.substr(startIndex - 1) - const index = shiftedText.indexOf(pattern) + startIndex - - return index > 0 ? index : new CellError(ErrorType.VALUE) - }) - } } From 9131caf587c14e07b86b16c8047eff7edc087121 Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 23 Jul 2020 18:03:05 +0200 Subject: [PATCH 35/97] linter --- src/interpreter/plugin/BooleanPlugin.ts | 2 +- test/interpreter/function-if.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interpreter/plugin/BooleanPlugin.ts b/src/interpreter/plugin/BooleanPlugin.ts index 81488c9031..ee6153b900 100644 --- a/src/interpreter/plugin/BooleanPlugin.ts +++ b/src/interpreter/plugin/BooleanPlugin.ts @@ -234,7 +234,7 @@ export class BooleanPlugin extends FunctionPlugin { } public choose(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithRepeatedArgNoRanges(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.CHOOSE.parameters, 1,(selector, ...args) => { + return this.runFunctionWithRepeatedArgNoRanges(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.CHOOSE.parameters, 1, (selector, ...args) => { if(selector !== Math.round(selector) || selector<1 || selector > args.length) { return new CellError(ErrorType.NUM) } diff --git a/test/interpreter/function-if.spec.ts b/test/interpreter/function-if.spec.ts index 98c7cd1aa1..39fd4dd3f8 100644 --- a/test/interpreter/function-if.spec.ts +++ b/test/interpreter/function-if.spec.ts @@ -53,7 +53,7 @@ describe('Function IF', () => { it('passes correct value when other arg is an error', () => { const engine = HyperFormula.buildFromArray([['=IF(FALSE(), 4/0, "no")']]) - expect(engine.getCellValue(adr('A1'))).toEqual("no") + expect(engine.getCellValue(adr('A1'))).toEqual('no') }) it('when condition is number', () => { From e4ba74b8cdbfe02916fb98e28a3212d282196e0f Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 23 Jul 2020 18:04:32 +0200 Subject: [PATCH 36/97] lint --- src/interpreter/plugin/MedianPlugin.ts | 2 +- src/interpreter/plugin/TrigonometryPlugin.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/interpreter/plugin/MedianPlugin.ts b/src/interpreter/plugin/MedianPlugin.ts index e61194cc62..e045b5dddf 100644 --- a/src/interpreter/plugin/MedianPlugin.ts +++ b/src/interpreter/plugin/MedianPlugin.ts @@ -4,7 +4,7 @@ */ import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' -import {AstNodeType, ProcedureAst} from '../../parser' +import {ProcedureAst} from '../../parser' import {FunctionPlugin} from './FunctionPlugin' /** diff --git a/src/interpreter/plugin/TrigonometryPlugin.ts b/src/interpreter/plugin/TrigonometryPlugin.ts index 99158a4efd..44212e9fe1 100644 --- a/src/interpreter/plugin/TrigonometryPlugin.ts +++ b/src/interpreter/plugin/TrigonometryPlugin.ts @@ -4,8 +4,7 @@ */ import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' -import {AstNodeType, ProcedureAst} from '../../parser' -import {SimpleRangeValue} from '../InterpreterValue' +import {ProcedureAst} from '../../parser' import {FunctionPlugin} from './FunctionPlugin' /** From 5b120700db989d90bdaac9be7321257023be4ddc Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 23 Jul 2020 18:09:32 +0200 Subject: [PATCH 37/97] countunique --- src/interpreter/plugin/CountUniquePlugin.ts | 30 ++++++++++----------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/interpreter/plugin/CountUniquePlugin.ts b/src/interpreter/plugin/CountUniquePlugin.ts index 0f5bfe8e83..477625b585 100644 --- a/src/interpreter/plugin/CountUniquePlugin.ts +++ b/src/interpreter/plugin/CountUniquePlugin.ts @@ -14,6 +14,9 @@ export class CountUniquePlugin extends FunctionPlugin { public static implementedFunctions = { 'COUNTUNIQUE': { method: 'countunique', + parameters: [ + { argumentType: 'scalar' }, + ], }, } @@ -26,24 +29,19 @@ export class CountUniquePlugin extends FunctionPlugin { * @param formulaAddress */ public countunique(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length === 0) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } + return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, CountUniquePlugin.implementedFunctions.COUNTUNIQUE.parameters, 1, (...args: InternalScalarValue[]) => { + const valuesSet = new Set() + const errorsSet = new Set() - const valuesSet = new Set() - const errorsSet = new Set() - - for (const scalarValue of this.iterateOverScalarValues(ast.args, formulaAddress)) { - if (scalarValue instanceof CellError) { - errorsSet.add(scalarValue.type) - } else if (scalarValue !== '') { - valuesSet.add(scalarValue) + for (const scalarValue of args) { + if (scalarValue instanceof CellError) { + errorsSet.add(scalarValue.type) + } else if (scalarValue !== '') { + valuesSet.add(scalarValue) + } } - } - return valuesSet.size + errorsSet.size + return valuesSet.size + errorsSet.size + }) } } From f584af3e65b54c8e787adcef61c26e222f70ffdb Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 23 Jul 2020 19:30:41 +0200 Subject: [PATCH 38/97] information plugin --- src/interpreter/plugin/InformationPlugin.ts | 108 +++++++------------- 1 file changed, 36 insertions(+), 72 deletions(-) diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index d91b608cb5..e163bf6218 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -16,21 +16,39 @@ export class InformationPlugin extends FunctionPlugin { public static implementedFunctions = { 'ISERROR': { method: 'iserror', + parameters: [ + { argumentType: 'scalar'} + ] }, 'ISBLANK': { method: 'isblank', + parameters: [ + { argumentType: 'scalar'} + ] }, 'ISNUMBER': { method: 'isnumber', + parameters: [ + { argumentType: 'scalar'} + ] }, 'ISLOGICAL': { method: 'islogical', + parameters: [ + { argumentType: 'scalar'} + ] }, 'ISTEXT': { method: 'istext', + parameters: [ + { argumentType: 'scalar'} + ] }, 'ISNONTEXT': { method: 'isnontext', + parameters: [ + { argumentType: 'scalar'} + ] }, 'COLUMNS': { method: 'columns', @@ -56,18 +74,9 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public iserror(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length != 1) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - const arg = this.evaluateAst(ast.args[0], formulaAddress) - if (arg instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - return (arg instanceof CellError) - + return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISERROR.parameters, (arg: InternalScalarValue) => + (arg instanceof CellError) + ) } /** @@ -79,18 +88,9 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public isblank(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length != 1) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - const arg = ast.args[0] - const value = this.evaluateAst(arg, formulaAddress) - if (value instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - return (value === EmptyValue) + return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISBLANK.parameters, (arg: InternalScalarValue) => + (arg === EmptyValue) + ) } /** @@ -102,18 +102,9 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public isnumber(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length != 1) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - const arg = ast.args[0] - const value = this.evaluateAst(arg, formulaAddress) - if (value instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - return (typeof value === 'number') + return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISNUMBER.parameters, (arg: InternalScalarValue) => + (typeof arg === 'number') + ) } /** @@ -125,18 +116,9 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public islogical(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length != 1) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - const arg = ast.args[0] - const value = this.evaluateAst(arg, formulaAddress) - if (value instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - return (typeof value === 'boolean') + return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISLOGICAL.parameters, (arg: InternalScalarValue) => + (typeof arg === 'boolean') + ) } /** @@ -148,18 +130,9 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public istext(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length != 1) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - const arg = ast.args[0] - const value = this.evaluateAst(arg, formulaAddress) - if (value instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - return (typeof value === 'string') + return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISTEXT.parameters, (arg: InternalScalarValue) => + (typeof arg === 'string') + ) } /** @@ -171,18 +144,9 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public isnontext(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length != 1) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - const arg = ast.args[0] - const value = this.evaluateAst(arg, formulaAddress) - if (value instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - return (typeof value !== 'string') + return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISNONTEXT.parameters, (arg: InternalScalarValue) => + (typeof arg !== 'string') + ) } /** From c956ca30c8f945b1a6ebff785876cf99a93e82f9 Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 23 Jul 2020 21:10:45 +0200 Subject: [PATCH 39/97] columns but its not working --- src/interpreter/plugin/FunctionPlugin.ts | 46 +++++++++++---------- src/interpreter/plugin/InformationPlugin.ts | 42 +++++++------------ 2 files changed, 40 insertions(+), 48 deletions(-) diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index a5750e4569..96825580de 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -31,7 +31,7 @@ export interface FunctionPluginDefinition { implementedFunctions: ImplementedFunctions, } -export type ArgumentTypes = 'string' | 'number' | 'boolean' | 'scalar' | 'noerror' +export type ArgumentTypes = 'string' | 'number' | 'boolean' | 'scalar' | 'noerror' | 'range' export interface FunctionArgumentDefinition { argumentType: string, @@ -149,18 +149,28 @@ export abstract class FunctionPlugin { public coerceScalarToNumberOrError = (arg: InternalScalarValue): number | CellError => this.interpreter.arithmeticHelper.coerceScalarToNumberOrError(arg) - public coerceToType(arg: InternalScalarValue, coercedType: ArgumentTypes): Maybe { - switch(coercedType) { - case 'number': - return this.coerceScalarToNumberOrError(arg) - case 'string': - return coerceScalarToString(arg) - case 'boolean': - return coerceScalarToBoolean(arg) - case 'scalar': - return arg - case 'noerror': + public coerceToType(arg: InterpreterValue, coercedType: ArgumentTypes): Maybe { + if(arg instanceof SimpleRangeValue) { + if(coercedType === 'range') { return arg + } else { + return new CellError(ErrorType.VALUE) + } + } else { + switch (coercedType) { + case 'number': + return this.coerceScalarToNumberOrError(arg) + case 'string': + return coerceScalarToString(arg) + case 'boolean': + return coerceScalarToBoolean(arg) + case 'scalar': + return arg + case 'noerror': + return arg + case 'range': + return new CellError(ErrorType.VALUE) + } } } @@ -174,16 +184,13 @@ export abstract class FunctionPlugin { return new CellError(ErrorType.NA) } - const coercedArguments: Maybe[] = [] + const coercedArguments: Maybe[] = [] for (let i = 0; i < argumentDefinitions.length; ++i) { if(args[i] === undefined && argumentDefinitions[i].defaultValue === undefined) { return new CellError(ErrorType.NA) } const arg = this.evaluateArgOrDefault(formulaAddress, args[i], argumentDefinitions[i].defaultValue) - if (arg instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } const coercedArg = this.coerceToType(arg, argumentDefinitions[i].argumentType as ArgumentTypes) if (coercedArg instanceof CellError && argumentDefinitions[i].argumentType !== 'scalar') { return coercedArg @@ -202,7 +209,7 @@ export abstract class FunctionPlugin { fn: (...arg: any) => InternalScalarValue ) => { const scalarValues: InternalScalarValue[] = [...this.iterateOverScalarValues(args, formulaAddress)] - const coercedArguments: Maybe[] = [] + const coercedArguments: Maybe[] = [] let j = 0 let i = 0 @@ -237,7 +244,7 @@ export abstract class FunctionPlugin { repeatedArgs: number, fn: (...arg: any) => InternalScalarValue ) => { - const coercedArguments: Maybe[] = [] + const coercedArguments: Maybe[] = [] let j = 0 let i = 0 //eslint-disable-next-line no-constant-condition @@ -246,9 +253,6 @@ export abstract class FunctionPlugin { return new CellError(ErrorType.NA) } const arg = this.evaluateArgOrDefault(formulaAddress, args[i], argumentDefinitions[j].defaultValue) - if (arg instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } const coercedArg = this.coerceToType(arg, argumentDefinitions[j].argumentType as ArgumentTypes) if (coercedArg instanceof CellError && argumentDefinitions[j].argumentType !== 'scalar') { return coercedArg diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index e163bf6218..b76b4fb2cd 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -5,7 +5,7 @@ import {AbsoluteCellRange} from '../../AbsoluteCellRange' import {CellError, EmptyValue, ErrorType, InternalCellValue, InternalScalarValue, SimpleCellAddress} from '../../Cell' -import {AstNodeType, ProcedureAst} from '../../parser' +import {AstNodeType, CellRangeAst, ProcedureAst} from '../../parser' import {SimpleRangeValue} from '../InterpreterValue' import {FunctionPlugin} from './FunctionPlugin' @@ -54,11 +54,17 @@ export class InformationPlugin extends FunctionPlugin { method: 'columns', isDependentOnSheetStructureChange: true, doesNotNeedArgumentsToBeComputed: true, + parameters: [ + { argumentType: 'range'} + ], }, 'ROWS': { method: 'rows', isDependentOnSheetStructureChange: true, doesNotNeedArgumentsToBeComputed: true, + parameters: [ + { argumentType: 'range'} + ], }, 'INDEX': { method: 'index', @@ -157,20 +163,11 @@ export class InformationPlugin extends FunctionPlugin { * @param ast * @param formulaAddress */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars public columns(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length !== 1) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - const rangeAst = ast.args[0] - if (rangeAst.type === AstNodeType.CELL_RANGE) { - return (rangeAst.end.col - rangeAst.start.col + 1) - } else { - return new CellError(ErrorType.VALUE) - } + return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.COLUMNS.parameters, () => { + const rangeAst = ast.args[0] as CellRangeAst + return rangeAst.end.col - rangeAst.start.col + 1 + }) } /** @@ -181,20 +178,11 @@ export class InformationPlugin extends FunctionPlugin { * @param ast * @param formulaAddress */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars public rows(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length !== 1) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - const rangeAst = ast.args[0] - if (rangeAst.type === AstNodeType.CELL_RANGE) { - return (rangeAst.end.row - rangeAst.start.row + 1) - } else { - return new CellError(ErrorType.VALUE) - } + return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ROWS.parameters, () => { + const rangeAst = ast.args[0] as CellRangeAst + return rangeAst.end.row - rangeAst.start.row + 1 + }) } public index(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalCellValue { From fbab7c0461fa1e35a70e02b2df9097ca2a8a3075 Mon Sep 17 00:00:00 2001 From: izulin Date: Fri, 24 Jul 2020 11:16:27 +0200 Subject: [PATCH 40/97] . --- src/interpreter/plugin/InformationPlugin.ts | 44 +++++++++++++-------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index b76b4fb2cd..32a817cbf2 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -5,8 +5,7 @@ import {AbsoluteCellRange} from '../../AbsoluteCellRange' import {CellError, EmptyValue, ErrorType, InternalCellValue, InternalScalarValue, SimpleCellAddress} from '../../Cell' -import {AstNodeType, CellRangeAst, ProcedureAst} from '../../parser' -import {SimpleRangeValue} from '../InterpreterValue' +import {AstNodeType, ProcedureAst} from '../../parser' import {FunctionPlugin} from './FunctionPlugin' /** @@ -54,17 +53,11 @@ export class InformationPlugin extends FunctionPlugin { method: 'columns', isDependentOnSheetStructureChange: true, doesNotNeedArgumentsToBeComputed: true, - parameters: [ - { argumentType: 'range'} - ], }, 'ROWS': { method: 'rows', isDependentOnSheetStructureChange: true, doesNotNeedArgumentsToBeComputed: true, - parameters: [ - { argumentType: 'range'} - ], }, 'INDEX': { method: 'index', @@ -154,7 +147,6 @@ export class InformationPlugin extends FunctionPlugin { (typeof arg !== 'string') ) } - /** * Corresponds to COLUMNS(range) * @@ -163,11 +155,20 @@ export class InformationPlugin extends FunctionPlugin { * @param ast * @param formulaAddress */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars public columns(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.COLUMNS.parameters, () => { - const rangeAst = ast.args[0] as CellRangeAst - return rangeAst.end.col - rangeAst.start.col + 1 - }) + if (ast.args.length !== 1) { + return new CellError(ErrorType.NA) + } + if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { + return new CellError(ErrorType.NUM) + } + const rangeAst = ast.args[0] + if (rangeAst.type === AstNodeType.CELL_RANGE) { + return (rangeAst.end.col - rangeAst.start.col + 1) + } else { + return new CellError(ErrorType.VALUE) + } } /** @@ -178,11 +179,20 @@ export class InformationPlugin extends FunctionPlugin { * @param ast * @param formulaAddress */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars public rows(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ROWS.parameters, () => { - const rangeAst = ast.args[0] as CellRangeAst - return rangeAst.end.row - rangeAst.start.row + 1 - }) + if (ast.args.length !== 1) { + return new CellError(ErrorType.NA) + } + if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { + return new CellError(ErrorType.NUM) + } + const rangeAst = ast.args[0] + if (rangeAst.type === AstNodeType.CELL_RANGE) { + return (rangeAst.end.row - rangeAst.start.row + 1) + } else { + return new CellError(ErrorType.VALUE) + } } public index(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalCellValue { From c5aa0f7f1df9660e61cf83f78ddf7f6ef01b2f22 Mon Sep 17 00:00:00 2001 From: izulin Date: Fri, 24 Jul 2020 12:33:42 +0200 Subject: [PATCH 41/97] fix --- src/interpreter/plugin/FunctionPlugin.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 96825580de..27fefb55f2 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -154,7 +154,7 @@ export abstract class FunctionPlugin { if(coercedType === 'range') { return arg } else { - return new CellError(ErrorType.VALUE) + return undefined } } else { switch (coercedType) { @@ -169,7 +169,7 @@ export abstract class FunctionPlugin { case 'noerror': return arg case 'range': - return new CellError(ErrorType.VALUE) + return undefined } } } @@ -192,6 +192,9 @@ export abstract class FunctionPlugin { } const arg = this.evaluateArgOrDefault(formulaAddress, args[i], argumentDefinitions[i].defaultValue) const coercedArg = this.coerceToType(arg, argumentDefinitions[i].argumentType as ArgumentTypes) + if(coercedArg === undefined) { + return new CellError(ErrorType.VALUE) + } if (coercedArg instanceof CellError && argumentDefinitions[i].argumentType !== 'scalar') { return coercedArg } @@ -220,6 +223,9 @@ export abstract class FunctionPlugin { return new CellError(ErrorType.NA) } const coercedArg = this.coerceToType(arg, argumentDefinitions[j].argumentType as ArgumentTypes) + if(coercedArg === undefined) { + new CellError(ErrorType.VALUE) + } if (coercedArg instanceof CellError && argumentDefinitions[j].argumentType !== 'scalar') { return coercedArg } @@ -254,6 +260,9 @@ export abstract class FunctionPlugin { } const arg = this.evaluateArgOrDefault(formulaAddress, args[i], argumentDefinitions[j].defaultValue) const coercedArg = this.coerceToType(arg, argumentDefinitions[j].argumentType as ArgumentTypes) + if(coercedArg === undefined) { + new CellError(ErrorType.VALUE) + } if (coercedArg instanceof CellError && argumentDefinitions[j].argumentType !== 'scalar') { return coercedArg } From ba3dcf8553748f97eeea7a2c545dec31dff30b03 Mon Sep 17 00:00:00 2001 From: izulin Date: Fri, 24 Jul 2020 12:46:07 +0200 Subject: [PATCH 42/97] . --- src/interpreter/plugin/BooleanPlugin.ts | 10 +++++----- src/interpreter/plugin/CountUniquePlugin.ts | 2 +- src/interpreter/plugin/FunctionPlugin.ts | 18 ++++++++---------- src/interpreter/plugin/MedianPlugin.ts | 2 +- src/interpreter/plugin/TextPlugin.ts | 2 +- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/interpreter/plugin/BooleanPlugin.ts b/src/interpreter/plugin/BooleanPlugin.ts index ee6153b900..5ebf415315 100644 --- a/src/interpreter/plugin/BooleanPlugin.ts +++ b/src/interpreter/plugin/BooleanPlugin.ts @@ -136,7 +136,7 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public and(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.AND.parameters, 1, (...args) => { + return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.AND.parameters, (...args) => { if(args.some((arg: Maybe) => arg===false)) { return false } else if(args.some((arg: Maybe) => arg!==undefined)) { @@ -156,7 +156,7 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public or(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.OR.parameters, 1, (...args) => { + return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.OR.parameters, (...args) => { if(args.some((arg: Maybe) => arg===true)) { return true } else if(args.some((arg: Maybe) => arg!==undefined)) { @@ -178,7 +178,7 @@ export class BooleanPlugin extends FunctionPlugin { } public xor(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.XOR.parameters, 1, (...args) => { + return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.XOR.parameters, (...args) => { if(args.some((arg: Maybe) => arg!==undefined)) { let cnt = 0 args.forEach((arg: Maybe) => { @@ -194,7 +194,7 @@ export class BooleanPlugin extends FunctionPlugin { } public switch(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithRepeatedArgNoRanges(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.SWITCH.parameters, 1, (selector, ...args) => { + return this.runFunctionWithRepeatedArgNoRanges(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.SWITCH.parameters, (selector, ...args) => { const n = args.length let i = 0 for (; i + 1 < n; i += 2) { @@ -234,7 +234,7 @@ export class BooleanPlugin extends FunctionPlugin { } public choose(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithRepeatedArgNoRanges(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.CHOOSE.parameters, 1, (selector, ...args) => { + return this.runFunctionWithRepeatedArgNoRanges(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.CHOOSE.parameters, (selector, ...args) => { if(selector !== Math.round(selector) || selector<1 || selector > args.length) { return new CellError(ErrorType.NUM) } diff --git a/src/interpreter/plugin/CountUniquePlugin.ts b/src/interpreter/plugin/CountUniquePlugin.ts index 477625b585..7f1f683f2b 100644 --- a/src/interpreter/plugin/CountUniquePlugin.ts +++ b/src/interpreter/plugin/CountUniquePlugin.ts @@ -29,7 +29,7 @@ export class CountUniquePlugin extends FunctionPlugin { * @param formulaAddress */ public countunique(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, CountUniquePlugin.implementedFunctions.COUNTUNIQUE.parameters, 1, (...args: InternalScalarValue[]) => { + return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, CountUniquePlugin.implementedFunctions.COUNTUNIQUE.parameters, (...args: InternalScalarValue[]) => { const valuesSet = new Set() const errorsSet = new Set() diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 27fefb55f2..fdba280aba 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -208,7 +208,6 @@ export abstract class FunctionPlugin { args: Ast[], formulaAddress: SimpleCellAddress, argumentDefinitions: FunctionArgumentDefinition[], - repeatedArgs: number, fn: (...arg: any) => InternalScalarValue ) => { const scalarValues: InternalScalarValue[] = [...this.iterateOverScalarValues(args, formulaAddress)] @@ -232,11 +231,11 @@ export abstract class FunctionPlugin { coercedArguments.push(coercedArg) j++ i++ - if(i >= scalarValues.length && j === argumentDefinitions.length) { - break - } if(j===argumentDefinitions.length) { - j -= repeatedArgs + if (i >= scalarValues.length) { + break + } + j-- } } @@ -247,7 +246,6 @@ export abstract class FunctionPlugin { args: Ast[], formulaAddress: SimpleCellAddress, argumentDefinitions: FunctionArgumentDefinition[], - repeatedArgs: number, fn: (...arg: any) => InternalScalarValue ) => { const coercedArguments: Maybe[] = [] @@ -269,11 +267,11 @@ export abstract class FunctionPlugin { coercedArguments.push(coercedArg) j++ i++ - if(i >= args.length && j === argumentDefinitions.length) { - break - } if(j===argumentDefinitions.length) { - j -= repeatedArgs + if (i >= args.length) { + break + } + j-- } } diff --git a/src/interpreter/plugin/MedianPlugin.ts b/src/interpreter/plugin/MedianPlugin.ts index e045b5dddf..1dab992ee7 100644 --- a/src/interpreter/plugin/MedianPlugin.ts +++ b/src/interpreter/plugin/MedianPlugin.ts @@ -30,7 +30,7 @@ export class MedianPlugin extends FunctionPlugin { * @param formulaAddress */ public median(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, MedianPlugin.implementedFunctions.MEDIAN.parameters, 1, (...args) => { + return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, MedianPlugin.implementedFunctions.MEDIAN.parameters, (...args) => { const values: number[] = args.filter((val: InternalScalarValue) => (typeof val === 'number')) if (values.length === 0) { return new CellError(ErrorType.NUM) diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index 1f85b6c1b1..dae5295989 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -86,7 +86,7 @@ export class TextPlugin extends FunctionPlugin { * @param formulaAddress */ public concatenate(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, TextPlugin.implementedFunctions.CONCATENATE.parameters, 1, (...args) => { + return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, TextPlugin.implementedFunctions.CONCATENATE.parameters, (...args) => { return ''.concat(...args) }) } From 0f3418251b96d02b1784a0f46b69a0fdb52ca360 Mon Sep 17 00:00:00 2001 From: izulin Date: Fri, 24 Jul 2020 17:13:47 +0200 Subject: [PATCH 43/97] minor fix --- src/interpreter/plugin/FunctionPlugin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index fdba280aba..ea3d29de3b 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -223,7 +223,7 @@ export abstract class FunctionPlugin { } const coercedArg = this.coerceToType(arg, argumentDefinitions[j].argumentType as ArgumentTypes) if(coercedArg === undefined) { - new CellError(ErrorType.VALUE) + return new CellError(ErrorType.VALUE) } if (coercedArg instanceof CellError && argumentDefinitions[j].argumentType !== 'scalar') { return coercedArg @@ -259,7 +259,7 @@ export abstract class FunctionPlugin { const arg = this.evaluateArgOrDefault(formulaAddress, args[i], argumentDefinitions[j].defaultValue) const coercedArg = this.coerceToType(arg, argumentDefinitions[j].argumentType as ArgumentTypes) if(coercedArg === undefined) { - new CellError(ErrorType.VALUE) + return new CellError(ErrorType.VALUE) } if (coercedArg instanceof CellError && argumentDefinitions[j].argumentType !== 'scalar') { return coercedArg From 7294051c3fdff7b1a05e9e490cff032d9cf450b7 Mon Sep 17 00:00:00 2001 From: izulin Date: Sat, 25 Jul 2020 13:03:37 +0200 Subject: [PATCH 44/97] simplified templates --- src/interpreter/ArithmeticHelper.ts | 8 +- src/interpreter/plugin/BooleanPlugin.ts | 36 ++++--- src/interpreter/plugin/CountUniquePlugin.ts | 4 +- src/interpreter/plugin/FinancialPlugin.ts | 8 +- src/interpreter/plugin/FunctionPlugin.ts | 100 +++++-------------- src/interpreter/plugin/InformationPlugin.ts | 12 +-- src/interpreter/plugin/MedianPlugin.ts | 4 +- src/interpreter/plugin/TextPlugin.ts | 18 ++-- src/interpreter/plugin/TrigonometryPlugin.ts | 2 +- 9 files changed, 75 insertions(+), 117 deletions(-) diff --git a/src/interpreter/ArithmeticHelper.ts b/src/interpreter/ArithmeticHelper.ts index 970ad07801..08fb88e222 100644 --- a/src/interpreter/ArithmeticHelper.ts +++ b/src/interpreter/ArithmeticHelper.ts @@ -225,13 +225,13 @@ export class ArithmeticHelper { return this.coerceToMaybeNumber(arg) ?? new CellError(ErrorType.VALUE) } - public coerceToMaybeNumber(arg: InternalNoErrorCellValue): Maybe { + public coerceToMaybeNumber(arg: InternalScalarValue): Maybe { return this.coerceNonDateScalarToMaybeNumber(arg) ?? ( typeof arg === 'string' ? this.dateTimeHelper.dateStringToDateNumber(arg) : undefined ) } - public coerceNonDateScalarToMaybeNumber(arg: InternalNoErrorCellValue): Maybe { + public coerceNonDateScalarToMaybeNumber(arg: InternalScalarValue): Maybe { if (arg === EmptyValue) { return 0 } else if (typeof arg === 'string' && this.numberLiteralsHelper.isNumber(arg)) { @@ -290,9 +290,7 @@ export function coerceEmptyToValue(arg: InternalNoErrorCellValue): InternalNoErr * @param arg */ export function coerceScalarToBoolean(arg: InternalScalarValue): boolean | CellError | undefined { - if (arg instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } else if (arg instanceof CellError || typeof arg === 'boolean') { + if (arg instanceof CellError || typeof arg === 'boolean') { return arg } else if (arg === EmptyValue) { return false diff --git a/src/interpreter/plugin/BooleanPlugin.ts b/src/interpreter/plugin/BooleanPlugin.ts index 5ebf415315..d33771e1f6 100644 --- a/src/interpreter/plugin/BooleanPlugin.ts +++ b/src/interpreter/plugin/BooleanPlugin.ts @@ -33,20 +33,26 @@ export class BooleanPlugin extends FunctionPlugin { 'AND': { method: 'and', parameters: [ - { argumentType: 'boolean' }, + { argumentType: 'boolean', softCoerce: true }, ], + repeatedArg: true, + expandRanges: true, }, 'OR': { method: 'or', parameters: [ - { argumentType: 'boolean' }, + { argumentType: 'boolean', softCoerce: true }, ], + repeatedArg: true, + expandRanges: true, }, 'XOR': { method: 'xor', parameters: [ - { argumentType: 'boolean' }, + { argumentType: 'boolean', softCoerce: true }, ], + repeatedArg: true, + expandRanges: true, }, 'NOT': { method: 'not', @@ -61,6 +67,7 @@ export class BooleanPlugin extends FunctionPlugin { { argumentType: 'scalar' }, { argumentType: 'scalar' }, ], + repeatedArg: true, }, 'IFERROR': { method: 'iferror', @@ -82,6 +89,7 @@ export class BooleanPlugin extends FunctionPlugin { { argumentType: 'number' }, { argumentType: 'scalar' }, ], + repeatedArg: true, }, } @@ -94,7 +102,7 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public literalTrue(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.TRUE.parameters, () => true) + return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.TRUE, () => true) } /** @@ -106,7 +114,7 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public literalFalse(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.FALSE.parameters, () => false) + return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.FALSE, () => false) } /** @@ -118,7 +126,7 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public conditionalIf(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InterpreterValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.IF.parameters, (condition, arg2, arg3) => { + return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.IF, (condition, arg2, arg3) => { if(condition===undefined) { return new CellError(ErrorType.VALUE) } else { @@ -136,7 +144,7 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public and(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.AND.parameters, (...args) => { + return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.AND, (...args) => { if(args.some((arg: Maybe) => arg===false)) { return false } else if(args.some((arg: Maybe) => arg!==undefined)) { @@ -156,7 +164,7 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public or(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.OR.parameters, (...args) => { + return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.OR, (...args) => { if(args.some((arg: Maybe) => arg===true)) { return true } else if(args.some((arg: Maybe) => arg!==undefined)) { @@ -168,7 +176,7 @@ export class BooleanPlugin extends FunctionPlugin { } public not(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.NOT.parameters, (arg) => { + return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.NOT, (arg) => { if(arg===undefined) { return new CellError(ErrorType.VALUE) } else { @@ -178,7 +186,7 @@ export class BooleanPlugin extends FunctionPlugin { } public xor(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.XOR.parameters, (...args) => { + return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.XOR, (...args) => { if(args.some((arg: Maybe) => arg!==undefined)) { let cnt = 0 args.forEach((arg: Maybe) => { @@ -194,7 +202,7 @@ export class BooleanPlugin extends FunctionPlugin { } public switch(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithRepeatedArgNoRanges(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.SWITCH.parameters, (selector, ...args) => { + return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.SWITCH, (selector, ...args) => { const n = args.length let i = 0 for (; i + 1 < n; i += 2) { @@ -214,7 +222,7 @@ export class BooleanPlugin extends FunctionPlugin { } public iferror(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.IFERROR.parameters, (arg1: InternalScalarValue, arg2: InternalScalarValue) => { + return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.IFERROR, (arg1: InternalScalarValue, arg2: InternalScalarValue) => { if(arg1 instanceof CellError) { return arg2 } else { @@ -224,7 +232,7 @@ export class BooleanPlugin extends FunctionPlugin { } public ifna(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.IFNA.parameters, (arg1: InternalScalarValue, arg2: InternalScalarValue) => { + return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.IFNA, (arg1: InternalScalarValue, arg2: InternalScalarValue) => { if(arg1 instanceof CellError && arg1.type === ErrorType.NA) { return arg2 } else { @@ -234,7 +242,7 @@ export class BooleanPlugin extends FunctionPlugin { } public choose(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithRepeatedArgNoRanges(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.CHOOSE.parameters, (selector, ...args) => { + return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.CHOOSE, (selector, ...args) => { if(selector !== Math.round(selector) || selector<1 || selector > args.length) { return new CellError(ErrorType.NUM) } diff --git a/src/interpreter/plugin/CountUniquePlugin.ts b/src/interpreter/plugin/CountUniquePlugin.ts index 7f1f683f2b..8dac701166 100644 --- a/src/interpreter/plugin/CountUniquePlugin.ts +++ b/src/interpreter/plugin/CountUniquePlugin.ts @@ -17,6 +17,8 @@ export class CountUniquePlugin extends FunctionPlugin { parameters: [ { argumentType: 'scalar' }, ], + repeatedArg: true, + expandRanges: true, }, } @@ -29,7 +31,7 @@ export class CountUniquePlugin extends FunctionPlugin { * @param formulaAddress */ public countunique(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, CountUniquePlugin.implementedFunctions.COUNTUNIQUE.parameters, (...args: InternalScalarValue[]) => { + return this.runFunction(ast.args, formulaAddress, CountUniquePlugin.implementedFunctions.COUNTUNIQUE, (...args: InternalScalarValue[]) => { const valuesSet = new Set() const errorsSet = new Set() diff --git a/src/interpreter/plugin/FinancialPlugin.ts b/src/interpreter/plugin/FinancialPlugin.ts index e31d644d4c..e6a26620d7 100644 --- a/src/interpreter/plugin/FinancialPlugin.ts +++ b/src/interpreter/plugin/FinancialPlugin.ts @@ -55,19 +55,19 @@ export class FinancialPlugin extends FunctionPlugin { } public pmt(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, FinancialPlugin.implementedFunctions.PMT.parameters, pmtCore) + return this.runFunction(ast.args, formulaAddress, FinancialPlugin.implementedFunctions.PMT, pmtCore) } public ipmt(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, FinancialPlugin.implementedFunctions.IPMT.parameters, ipmtCore) + return this.runFunction(ast.args, formulaAddress, FinancialPlugin.implementedFunctions.IPMT, ipmtCore) } public ppmt(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, FinancialPlugin.implementedFunctions.PPMT.parameters, ppmtCore) + return this.runFunction(ast.args, formulaAddress, FinancialPlugin.implementedFunctions.PPMT, ppmtCore) } public fv(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, FinancialPlugin.implementedFunctions.FV.parameters, fvCore) + return this.runFunction(ast.args, formulaAddress, FinancialPlugin.implementedFunctions.FV, fvCore) } } diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index ea3d29de3b..0d1ccd5086 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -36,6 +36,7 @@ export type ArgumentTypes = 'string' | 'number' | 'boolean' | 'scalar' | 'noerro export interface FunctionArgumentDefinition { argumentType: string, defaultValue?: InternalScalarValue, + softCoerce?: boolean } export type PluginFunctionType = (ast: ProcedureAst, formulaAddress: SimpleCellAddress) => InternalScalarValue @@ -90,11 +91,11 @@ export abstract class FunctionPlugin { } protected templateWithOneCoercedToNumberArgument(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (arg: number) => InternalScalarValue): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, [{ argumentType: 'number'}], fn) + return this.runFunction(ast.args, formulaAddress, {parameters: [{ argumentType: 'number'}]}, fn) } protected templateWithOneCoercedToStringArgument(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (arg: string) => InternalScalarValue): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, [{ argumentType: 'string' }], fn) + return this.runFunction(ast.args, formulaAddress, {parameters: [{ argumentType: 'string' }]}, fn) } protected validateTwoNumericArguments(ast: ProcedureAst, formulaAddress: SimpleCellAddress): [number, number] | CellError { @@ -174,108 +175,53 @@ export abstract class FunctionPlugin { } } - protected runFunctionWithDefaults = ( + protected runFunction = ( args: Ast[], formulaAddress: SimpleCellAddress, - argumentDefinitions: FunctionArgumentDefinition[], + functionDefinition: any, fn: (...arg: any) => InternalScalarValue ) => { - if (args.length > argumentDefinitions.length) { - return new CellError(ErrorType.NA) - } - - const coercedArguments: Maybe[] = [] - - for (let i = 0; i < argumentDefinitions.length; ++i) { - if(args[i] === undefined && argumentDefinitions[i].defaultValue === undefined) { - return new CellError(ErrorType.NA) - } - const arg = this.evaluateArgOrDefault(formulaAddress, args[i], argumentDefinitions[i].defaultValue) - const coercedArg = this.coerceToType(arg, argumentDefinitions[i].argumentType as ArgumentTypes) - if(coercedArg === undefined) { - return new CellError(ErrorType.VALUE) - } - if (coercedArg instanceof CellError && argumentDefinitions[i].argumentType !== 'scalar') { - return coercedArg - } - coercedArguments.push(coercedArg) + const argumentDefinitions: FunctionArgumentDefinition[] = functionDefinition.parameters + let scalarValues: InterpreterValue[] + if(functionDefinition.expandRanges) { + scalarValues = [...this.iterateOverScalarValues(args, formulaAddress)] + } else { + scalarValues = args.map((ast) => this.evaluateAst(ast, formulaAddress)) } - - return fn(...coercedArguments) - } - - protected runFunctionWithRepeatedArg = ( - args: Ast[], - formulaAddress: SimpleCellAddress, - argumentDefinitions: FunctionArgumentDefinition[], - fn: (...arg: any) => InternalScalarValue - ) => { - const scalarValues: InternalScalarValue[] = [...this.iterateOverScalarValues(args, formulaAddress)] const coercedArguments: Maybe[] = [] + let argCoerceFailure: Maybe = undefined let j = 0 let i = 0 //eslint-disable-next-line no-constant-condition while(true) { - const arg = scalarValues[i] ?? argumentDefinitions[j].defaultValue - if(arg === undefined) { - return new CellError(ErrorType.NA) - } - const coercedArg = this.coerceToType(arg, argumentDefinitions[j].argumentType as ArgumentTypes) - if(coercedArg === undefined) { - return new CellError(ErrorType.VALUE) - } - if (coercedArg instanceof CellError && argumentDefinitions[j].argumentType !== 'scalar') { - return coercedArg - } - coercedArguments.push(coercedArg) - j++ - i++ if(j===argumentDefinitions.length) { if (i >= scalarValues.length) { break } - j-- + if(functionDefinition.repeatedArg) { + j-- + } else { + return new CellError(ErrorType.NA) + } } - } - - return fn(...coercedArguments) - } - - protected runFunctionWithRepeatedArgNoRanges = ( - args: Ast[], - formulaAddress: SimpleCellAddress, - argumentDefinitions: FunctionArgumentDefinition[], - fn: (...arg: any) => InternalScalarValue - ) => { - const coercedArguments: Maybe[] = [] - let j = 0 - let i = 0 - //eslint-disable-next-line no-constant-condition - while(true) { - if(args[i] === undefined && argumentDefinitions[j].defaultValue === undefined) { + const arg = scalarValues[i] ?? argumentDefinitions[j]?.defaultValue + if(arg === undefined) { return new CellError(ErrorType.NA) } - const arg = this.evaluateArgOrDefault(formulaAddress, args[i], argumentDefinitions[j].defaultValue) const coercedArg = this.coerceToType(arg, argumentDefinitions[j].argumentType as ArgumentTypes) - if(coercedArg === undefined) { - return new CellError(ErrorType.VALUE) + if(coercedArg === undefined && !argumentDefinitions[j].softCoerce) { + argCoerceFailure = argCoerceFailure ?? (new CellError(ErrorType.VALUE)) } if (coercedArg instanceof CellError && argumentDefinitions[j].argumentType !== 'scalar') { - return coercedArg + argCoerceFailure = argCoerceFailure ?? coercedArg } coercedArguments.push(coercedArg) j++ i++ - if(j===argumentDefinitions.length) { - if (i >= args.length) { - break - } - j-- - } } - return fn(...coercedArguments) + return argCoerceFailure ?? fn(...coercedArguments) } protected evaluateArgOrDefault = (formulaAddress: SimpleCellAddress, argAst?: Ast, defaultValue?: InternalScalarValue): InterpreterValue => { diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index 32a817cbf2..99b9b2828a 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -73,7 +73,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public iserror(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISERROR.parameters, (arg: InternalScalarValue) => + return this.runFunction(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISERROR, (arg: InternalScalarValue) => (arg instanceof CellError) ) } @@ -87,7 +87,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public isblank(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISBLANK.parameters, (arg: InternalScalarValue) => + return this.runFunction(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISBLANK, (arg: InternalScalarValue) => (arg === EmptyValue) ) } @@ -101,7 +101,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public isnumber(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISNUMBER.parameters, (arg: InternalScalarValue) => + return this.runFunction(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISNUMBER, (arg: InternalScalarValue) => (typeof arg === 'number') ) } @@ -115,7 +115,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public islogical(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISLOGICAL.parameters, (arg: InternalScalarValue) => + return this.runFunction(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISLOGICAL, (arg: InternalScalarValue) => (typeof arg === 'boolean') ) } @@ -129,7 +129,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public istext(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISTEXT.parameters, (arg: InternalScalarValue) => + return this.runFunction(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISTEXT, (arg: InternalScalarValue) => (typeof arg === 'string') ) } @@ -143,7 +143,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public isnontext(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISNONTEXT.parameters, (arg: InternalScalarValue) => + return this.runFunction(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISNONTEXT, (arg: InternalScalarValue) => (typeof arg !== 'string') ) } diff --git a/src/interpreter/plugin/MedianPlugin.ts b/src/interpreter/plugin/MedianPlugin.ts index 1dab992ee7..11f7cba99d 100644 --- a/src/interpreter/plugin/MedianPlugin.ts +++ b/src/interpreter/plugin/MedianPlugin.ts @@ -18,6 +18,8 @@ export class MedianPlugin extends FunctionPlugin { parameters: [ { argumentType: 'noerror' }, ], + repeatedArg: true, + expandRanges: true, }, } @@ -30,7 +32,7 @@ export class MedianPlugin extends FunctionPlugin { * @param formulaAddress */ public median(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, MedianPlugin.implementedFunctions.MEDIAN.parameters, (...args) => { + return this.runFunction(ast.args, formulaAddress, MedianPlugin.implementedFunctions.MEDIAN, (...args) => { const values: number[] = args.filter((val: InternalScalarValue) => (typeof val === 'number')) if (values.length === 0) { return new CellError(ErrorType.NUM) diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index dae5295989..89a6e6279c 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -17,7 +17,9 @@ export class TextPlugin extends FunctionPlugin { method: 'concatenate', parameters: [ { argumentType: 'string'} - ] + ], + repeatedArg: true, + expandRanges: true, }, 'SPLIT': { method: 'split', @@ -86,7 +88,7 @@ export class TextPlugin extends FunctionPlugin { * @param formulaAddress */ public concatenate(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithRepeatedArg(ast.args, formulaAddress, TextPlugin.implementedFunctions.CONCATENATE.parameters, (...args) => { + return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.CONCATENATE, (...args) => { return ''.concat(...args) }) } @@ -100,7 +102,7 @@ export class TextPlugin extends FunctionPlugin { * @param formulaAddress */ public split(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.SPLIT.parameters, (stringToSplit: string, indexToUse: number) => { + return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.SPLIT, (stringToSplit: string, indexToUse: number) => { const splittedString = stringToSplit.split(' ') if (indexToUse >= splittedString.length || indexToUse < 0) { @@ -137,7 +139,7 @@ export class TextPlugin extends FunctionPlugin { } public rept(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.REPT.parameters, (text: string, count: number) => { + return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.REPT, (text: string, count: number) => { if (count < 0) { return new CellError(ErrorType.VALUE) } @@ -146,7 +148,7 @@ export class TextPlugin extends FunctionPlugin { } public right(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.RIGHT.parameters, (text: string, length: number) => { + return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.RIGHT, (text: string, length: number) => { if (length < 0) { return new CellError(ErrorType.VALUE) } else if (length === 0) { @@ -157,7 +159,7 @@ export class TextPlugin extends FunctionPlugin { } public left(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.LEFT.parameters, (text: string, length: number) => { + return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.LEFT, (text: string, length: number) => { if (length < 0) { return new CellError(ErrorType.VALUE) } @@ -166,7 +168,7 @@ export class TextPlugin extends FunctionPlugin { } public search(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.SEARCH.parameters, (pattern, text: string, startIndex: number) => { + return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.SEARCH, (pattern, text: string, startIndex: number) => { if (startIndex < 1 || startIndex > text.length) { return new CellError(ErrorType.VALUE) } @@ -186,7 +188,7 @@ export class TextPlugin extends FunctionPlugin { } public find(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, TextPlugin.implementedFunctions.FIND.parameters, (pattern, text: string, startIndex: number) => { + return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.FIND, (pattern, text: string, startIndex: number) => { if (startIndex < 1 || startIndex > text.length) { return new CellError(ErrorType.VALUE) } diff --git a/src/interpreter/plugin/TrigonometryPlugin.ts b/src/interpreter/plugin/TrigonometryPlugin.ts index 44212e9fe1..c2cae091e9 100644 --- a/src/interpreter/plugin/TrigonometryPlugin.ts +++ b/src/interpreter/plugin/TrigonometryPlugin.ts @@ -88,7 +88,7 @@ export class TrigonometryPlugin extends FunctionPlugin { } public atan2(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, TrigonometryPlugin.implementedFunctions.ATAN2.parameters, Math.atan2) + return this.runFunction(ast.args, formulaAddress, TrigonometryPlugin.implementedFunctions.ATAN2, Math.atan2) } public ctg(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { From 65caf1f6bb3b8e642e3f0feee96189d0b08e6afd Mon Sep 17 00:00:00 2001 From: izulin Date: Sat, 25 Jul 2020 13:32:59 +0200 Subject: [PATCH 45/97] linter + typing --- src/interpreter/plugin/FunctionPlugin.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 0d1ccd5086..a2d30780b2 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -3,6 +3,7 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ +import assert from 'assert' import {AbsoluteCellRange} from '../../AbsoluteCellRange' import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ColumnSearchStrategy} from '../../ColumnSearch/ColumnSearchStrategy' @@ -20,6 +21,9 @@ export interface ImplementedFunctions { export interface FunctionMetadata { method: string, + parameters?: FunctionArgumentDefinition[], + repeatedArg?: boolean, + expandRanges?: boolean, isVolatile?: boolean, isDependentOnSheetStructureChange?: boolean, doesNotNeedArgumentsToBeComputed?: boolean, @@ -36,7 +40,7 @@ export type ArgumentTypes = 'string' | 'number' | 'boolean' | 'scalar' | 'noerro export interface FunctionArgumentDefinition { argumentType: string, defaultValue?: InternalScalarValue, - softCoerce?: boolean + softCoerce?: boolean, } export type PluginFunctionType = (ast: ProcedureAst, formulaAddress: SimpleCellAddress) => InternalScalarValue @@ -91,11 +95,11 @@ export abstract class FunctionPlugin { } protected templateWithOneCoercedToNumberArgument(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (arg: number) => InternalScalarValue): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, {parameters: [{ argumentType: 'number'}]}, fn) + return this.runFunction(ast.args, formulaAddress, {parameters: [{ argumentType: 'number'}]} as FunctionMetadata, fn) } protected templateWithOneCoercedToStringArgument(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (arg: string) => InternalScalarValue): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, {parameters: [{ argumentType: 'string' }]}, fn) + return this.runFunction(ast.args, formulaAddress, {parameters: [{ argumentType: 'string' }]} as FunctionMetadata, fn) } protected validateTwoNumericArguments(ast: ProcedureAst, formulaAddress: SimpleCellAddress): [number, number] | CellError { @@ -178,10 +182,11 @@ export abstract class FunctionPlugin { protected runFunction = ( args: Ast[], formulaAddress: SimpleCellAddress, - functionDefinition: any, + functionDefinition: FunctionMetadata, fn: (...arg: any) => InternalScalarValue ) => { - const argumentDefinitions: FunctionArgumentDefinition[] = functionDefinition.parameters + const argumentDefinitions: FunctionArgumentDefinition[] = functionDefinition.parameters! + assert(argumentDefinitions !== undefined) let scalarValues: InterpreterValue[] if(functionDefinition.expandRanges) { scalarValues = [...this.iterateOverScalarValues(args, formulaAddress)] From fc5d84fd4640b48a35690170d6ac83376febf72a Mon Sep 17 00:00:00 2001 From: izulin Date: Sat, 25 Jul 2020 15:56:29 +0200 Subject: [PATCH 46/97] template simplification --- src/interpreter/plugin/AbsPlugin.ts | 7 +- src/interpreter/plugin/CharPlugin.ts | 5 +- src/interpreter/plugin/CodePlugin.ts | 5 +- src/interpreter/plugin/DegreesPlugin.ts | 5 +- src/interpreter/plugin/ErrorFunctionPlugin.ts | 7 +- src/interpreter/plugin/ExpPlugin.ts | 7 +- src/interpreter/plugin/FunctionPlugin.ts | 14 +--- src/interpreter/plugin/LogarithmPlugin.ts | 68 ++++++------------- src/interpreter/plugin/ModuloPlugin.ts | 22 +++--- src/interpreter/plugin/PowerPlugin.ts | 23 ++----- src/interpreter/plugin/RadiansPlugin.ts | 9 ++- src/interpreter/plugin/RoundingPlugin.ts | 15 +++- src/interpreter/plugin/SqrtPlugin.ts | 11 ++- src/interpreter/plugin/TextPlugin.ts | 28 +++++--- src/interpreter/plugin/TrigonometryPlugin.ts | 47 +++++++------ test/interpreter/function-log.spec.ts | 6 ++ 16 files changed, 136 insertions(+), 143 deletions(-) diff --git a/src/interpreter/plugin/AbsPlugin.ts b/src/interpreter/plugin/AbsPlugin.ts index 148c64faa5..34de534580 100644 --- a/src/interpreter/plugin/AbsPlugin.ts +++ b/src/interpreter/plugin/AbsPlugin.ts @@ -11,12 +11,13 @@ export class AbsPlugin extends FunctionPlugin { public static implementedFunctions = { 'ABS': { method: 'abs', + parameters: [ + { argumentType: 'number' } + ], }, } public abs(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, (arg) => { - return Math.abs(arg) - }) + return this.runFunction(ast.args, formulaAddress, AbsPlugin.implementedFunctions.ABS, Math.abs) } } diff --git a/src/interpreter/plugin/CharPlugin.ts b/src/interpreter/plugin/CharPlugin.ts index 454d08be6a..e1f6e62c2e 100644 --- a/src/interpreter/plugin/CharPlugin.ts +++ b/src/interpreter/plugin/CharPlugin.ts @@ -11,11 +11,14 @@ export class CharPlugin extends FunctionPlugin { public static implementedFunctions = { 'CHAR': { method: 'char', + parameters: [ + { argumentType: 'number' } + ], }, } public char(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, (value: number) => { + return this.runFunction(ast.args, formulaAddress, CharPlugin.implementedFunctions.CHAR,(value: number) => { if (value < 1 || value > 255) { return new CellError(ErrorType.NUM) } diff --git a/src/interpreter/plugin/CodePlugin.ts b/src/interpreter/plugin/CodePlugin.ts index 03c6c035f2..9fbc7e7b4e 100644 --- a/src/interpreter/plugin/CodePlugin.ts +++ b/src/interpreter/plugin/CodePlugin.ts @@ -11,11 +11,14 @@ export class CodePlugin extends FunctionPlugin { public static implementedFunctions = { 'CODE': { method: 'code', + parameters: [ + { argumentType: 'string' } + ], }, } public code(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToStringArgument(ast, formulaAddress, (value: string) => { + return this.runFunction(ast.args, formulaAddress, CodePlugin.implementedFunctions.CODE,(value: string) => { if (value.length === 0) { return new CellError(ErrorType.VALUE) } diff --git a/src/interpreter/plugin/DegreesPlugin.ts b/src/interpreter/plugin/DegreesPlugin.ts index 69515316c2..328b065b34 100644 --- a/src/interpreter/plugin/DegreesPlugin.ts +++ b/src/interpreter/plugin/DegreesPlugin.ts @@ -11,11 +11,14 @@ export class DegreesPlugin extends FunctionPlugin { public static implementedFunctions = { 'DEGREES': { method: 'degrees', + parameters: [ + { argumentType: 'number' } + ], }, } public degrees(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, (arg) => { + return this.runFunction(ast.args, formulaAddress, DegreesPlugin.implementedFunctions.DEGREES,(arg) => { return arg * (180 / Math.PI) }) } diff --git a/src/interpreter/plugin/ErrorFunctionPlugin.ts b/src/interpreter/plugin/ErrorFunctionPlugin.ts index 7a7cce454a..b14f24cc49 100644 --- a/src/interpreter/plugin/ErrorFunctionPlugin.ts +++ b/src/interpreter/plugin/ErrorFunctionPlugin.ts @@ -14,6 +14,9 @@ export class ErrorFunctionPlugin extends FunctionPlugin { }, 'ERFC': { method: 'erfc', + parameters: [ + { argumentType: 'number' } + ], }, } @@ -42,9 +45,7 @@ export class ErrorFunctionPlugin extends FunctionPlugin { } public erfc(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, (arg: number) => { - return erfc(arg) - }) + return this.runFunction(ast.args, formulaAddress, ErrorFunctionPlugin.implementedFunctions.ERFC,erfc) } } diff --git a/src/interpreter/plugin/ExpPlugin.ts b/src/interpreter/plugin/ExpPlugin.ts index 2d8f173735..af1286cedd 100644 --- a/src/interpreter/plugin/ExpPlugin.ts +++ b/src/interpreter/plugin/ExpPlugin.ts @@ -11,6 +11,9 @@ export class ExpPlugin extends FunctionPlugin { public static implementedFunctions = { 'EXP': { method: 'exp', + parameters: [ + { argumentType: 'number' } + ], }, } @@ -23,8 +26,6 @@ export class ExpPlugin extends FunctionPlugin { * @param formulaAddress */ public exp(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, (arg) => { - return Math.exp(arg) - }) + return this.runFunction(ast.args, formulaAddress, ExpPlugin.implementedFunctions.EXP, Math.exp) } } diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index a2d30780b2..bbff78d536 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -94,14 +94,6 @@ export abstract class FunctionPlugin { return values } - protected templateWithOneCoercedToNumberArgument(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (arg: number) => InternalScalarValue): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, {parameters: [{ argumentType: 'number'}]} as FunctionMetadata, fn) - } - - protected templateWithOneCoercedToStringArgument(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (arg: string) => InternalScalarValue): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, {parameters: [{ argumentType: 'string' }]} as FunctionMetadata, fn) - } - protected validateTwoNumericArguments(ast: ProcedureAst, formulaAddress: SimpleCellAddress): [number, number] | CellError { if (ast.args.length !== 2) { return new CellError(ErrorType.NA) @@ -198,12 +190,8 @@ export abstract class FunctionPlugin { let argCoerceFailure: Maybe = undefined let j = 0 let i = 0 - //eslint-disable-next-line no-constant-condition - while(true) { + while(i= scalarValues.length) { - break - } if(functionDefinition.repeatedArg) { j-- } else { diff --git a/src/interpreter/plugin/LogarithmPlugin.ts b/src/interpreter/plugin/LogarithmPlugin.ts index 2187c3517d..6154690806 100644 --- a/src/interpreter/plugin/LogarithmPlugin.ts +++ b/src/interpreter/plugin/LogarithmPlugin.ts @@ -13,70 +13,40 @@ export class LogarithmPlugin extends FunctionPlugin { public static implementedFunctions = { 'LOG10': { method: 'log10', + parameters: [ + { argumentType: 'number' } + ], }, 'LOG': { method: 'log', + parameters: [ + { argumentType: 'number' }, + { argumentType: 'number', defaultValue: 10 }, + ], }, 'LN': { method: 'ln', + parameters: [ + { argumentType: 'number' } + ], }, } public log10(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, (arg) => { - if (arg > 0) { - return Math.log10(arg) - } else { - return new CellError(ErrorType.NUM) - } - }) + return this.runFunction(ast.args, formulaAddress, LogarithmPlugin.implementedFunctions.LOG10, Math.log10) } public log(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length < 1 || ast.args.length > 2) { - return new CellError(ErrorType.NA) - } - - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - const arg = this.evaluateAst(ast.args[0], formulaAddress) - if (arg instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - let coercedLogarithmicBase - if (ast.args[1]) { - const logarithmicBase = this.evaluateAst(ast.args[1], formulaAddress) - if (logarithmicBase instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - coercedLogarithmicBase = this.coerceScalarToNumberOrError(logarithmicBase) - } else { - coercedLogarithmicBase = 10 - } - - const coercedArg = this.coerceScalarToNumberOrError(arg) - if (coercedArg instanceof CellError) { - return coercedArg - } else if (coercedLogarithmicBase instanceof CellError) { - return coercedLogarithmicBase - } else { - if (coercedArg > 0 && coercedLogarithmicBase > 0) { - return (Math.log(coercedArg) / Math.log(coercedLogarithmicBase)) - } else { - return new CellError(ErrorType.NUM) - } - } + return this.runFunction(ast.args, formulaAddress, LogarithmPlugin.implementedFunctions.LOG, (arg: number, base: number) => { + if(arg > 0 && base > 0) { + return Math.log(arg) / Math.log(base) + } else { + return new CellError(ErrorType.NUM) + } + }) } public ln(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, (arg) => { - if (arg > 0) { - return Math.log(arg) - } else { - return new CellError(ErrorType.NUM) - } - }) + return this.runFunction(ast.args, formulaAddress, LogarithmPlugin.implementedFunctions.LN,Math.log) } } diff --git a/src/interpreter/plugin/ModuloPlugin.ts b/src/interpreter/plugin/ModuloPlugin.ts index 5e2e9e998f..e5f5ea3e35 100644 --- a/src/interpreter/plugin/ModuloPlugin.ts +++ b/src/interpreter/plugin/ModuloPlugin.ts @@ -11,20 +11,20 @@ export class ModuloPlugin extends FunctionPlugin { public static implementedFunctions = { 'MOD': { method: 'mod', + parameters: [ + { argumentType: 'number' }, + { argumentType: 'number' }, + ], }, } public mod(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - const validationResult = this.validateTwoNumericArguments(ast, formulaAddress) - if (validationResult instanceof CellError) { - return validationResult - } - const [dividend, divisor] = validationResult - - if (divisor === 0) { - return new CellError(ErrorType.DIV_BY_ZERO) - } - - return dividend % divisor + return this.runFunction(ast.args, formulaAddress, ModuloPlugin.implementedFunctions.MOD, (dividend: number, divisor: number) => { + if (divisor === 0) { + return new CellError(ErrorType.DIV_BY_ZERO) + } else { + return dividend % divisor + } + }) } } diff --git a/src/interpreter/plugin/PowerPlugin.ts b/src/interpreter/plugin/PowerPlugin.ts index 5d90ad65cc..53150065b3 100644 --- a/src/interpreter/plugin/PowerPlugin.ts +++ b/src/interpreter/plugin/PowerPlugin.ts @@ -11,27 +11,14 @@ export class PowerPlugin extends FunctionPlugin { public static implementedFunctions = { 'POWER': { method: 'power', + parameters: [ + { argumentType: 'number' }, + { argumentType: 'number' }, + ], }, } public power(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - const validationResult = this.validateTwoNumericArguments(ast, formulaAddress) - - if (validationResult instanceof CellError) { - return validationResult - } - - const [coercedBase, coercedExponent] = validationResult - - if (coercedBase === 0 && coercedExponent < 0) { - return new CellError(ErrorType.NUM) - } - - const value = Math.pow(coercedBase, coercedExponent) - if (!Number.isFinite(value)) { - return new CellError(ErrorType.NUM) - } else { - return value - } + return this.runFunction(ast.args, formulaAddress, PowerPlugin.implementedFunctions.POWER, Math.pow) } } diff --git a/src/interpreter/plugin/RadiansPlugin.ts b/src/interpreter/plugin/RadiansPlugin.ts index c7d06318c1..e673b84b4b 100644 --- a/src/interpreter/plugin/RadiansPlugin.ts +++ b/src/interpreter/plugin/RadiansPlugin.ts @@ -11,12 +11,15 @@ export class RadiansPlugin extends FunctionPlugin { public static implementedFunctions = { 'RADIANS': { method: 'radians', + parameters: [ + { argumentType: 'number' } + ], }, } public radians(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, (arg) => { - return arg * (Math.PI / 180) - }) + return this.runFunction(ast.args, formulaAddress, RadiansPlugin.implementedFunctions.RADIANS, + (arg) => arg * (Math.PI / 180) + ) } } diff --git a/src/interpreter/plugin/RoundingPlugin.ts b/src/interpreter/plugin/RoundingPlugin.ts index bbe2e4222b..a13c488798 100644 --- a/src/interpreter/plugin/RoundingPlugin.ts +++ b/src/interpreter/plugin/RoundingPlugin.ts @@ -36,12 +36,21 @@ export class RoundingPlugin extends FunctionPlugin { }, 'INT': { method: 'intFunc', + parameters: [ + { argumentType: 'number' } + ], }, 'EVEN': { method: 'even', + parameters: [ + { argumentType: 'number' } + ], }, 'ODD': { method: 'odd', + parameters: [ + { argumentType: 'number' } + ], }, 'CEILING': { method: 'ceiling', @@ -86,7 +95,7 @@ export class RoundingPlugin extends FunctionPlugin { } public intFunc(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, (coercedNumberToRound) => { + return this.runFunction(ast.args, formulaAddress, RoundingPlugin.implementedFunctions.INT, (coercedNumberToRound) => { if (coercedNumberToRound < 0) { return -Math.floor(-coercedNumberToRound) } else { @@ -96,7 +105,7 @@ export class RoundingPlugin extends FunctionPlugin { } public even(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, (coercedNumberToRound) => { + return this.runFunction(ast.args, formulaAddress, RoundingPlugin.implementedFunctions.EVEN,(coercedNumberToRound) => { if (coercedNumberToRound < 0) { return -findNextEvenNumber(-coercedNumberToRound) } else { @@ -106,7 +115,7 @@ export class RoundingPlugin extends FunctionPlugin { } public odd(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, (coercedNumberToRound) => { + return this.runFunction(ast.args, formulaAddress, RoundingPlugin.implementedFunctions.ODD,(coercedNumberToRound) => { if (coercedNumberToRound < 0) { return -findNextOddNumber(-coercedNumberToRound) } else { diff --git a/src/interpreter/plugin/SqrtPlugin.ts b/src/interpreter/plugin/SqrtPlugin.ts index b3f2a278c2..93734de57b 100644 --- a/src/interpreter/plugin/SqrtPlugin.ts +++ b/src/interpreter/plugin/SqrtPlugin.ts @@ -11,16 +11,13 @@ export class SqrtPlugin extends FunctionPlugin { public static implementedFunctions = { 'SQRT': { method: 'sqrt', + parameters: [ + { argumentType: 'number' } + ], }, } public sqrt(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, (input: number) => { - if (input < 0) { - return new CellError(ErrorType.NUM) - } else { - return Math.sqrt(input) - } - }) + return this.runFunction(ast.args, formulaAddress, SqrtPlugin.implementedFunctions.SQRT, Math.sqrt) } } diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index 89a6e6279c..7db5dd3c38 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -29,16 +29,28 @@ export class TextPlugin extends FunctionPlugin { ], }, 'LEN': { - method: 'len' + method: 'len', + parameters: [ + { argumentType: 'string'} + ], }, 'TRIM': { - method: 'trim' + method: 'trim', + parameters: [ + { argumentType: 'string'} + ], }, 'PROPER': { - method: 'proper' + method: 'proper', + parameters: [ + { argumentType: 'string'} + ], }, 'CLEAN': { - method: 'clean' + method: 'clean', + parameters: [ + { argumentType: 'string'} + ], }, 'REPT': { method: 'rept', @@ -114,25 +126,25 @@ export class TextPlugin extends FunctionPlugin { } public len(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToStringArgument(ast, formulaAddress, (arg: string) => { + return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.LEN, (arg: string) => { return arg.length }) } public trim(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToStringArgument(ast, formulaAddress, (arg: string) => { + return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.TRIM, (arg: string) => { return arg.replace(/^ +| +$/g, '').replace(/ +/g, ' ') }) } public proper(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToStringArgument(ast, formulaAddress, (arg: string) => { + return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.PROPER, (arg: string) => { return arg.replace(/\w\S*/g, word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase()) }) } public clean(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToStringArgument(ast, formulaAddress, (arg: string) => { + return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.CLEAN, (arg: string) => { // eslint-disable-next-line no-control-regex return arg.replace(/[\u0000-\u001F]/g, '') }) diff --git a/src/interpreter/plugin/TrigonometryPlugin.ts b/src/interpreter/plugin/TrigonometryPlugin.ts index c2cae091e9..4b6365f125 100644 --- a/src/interpreter/plugin/TrigonometryPlugin.ts +++ b/src/interpreter/plugin/TrigonometryPlugin.ts @@ -15,21 +15,39 @@ export class TrigonometryPlugin extends FunctionPlugin { public static implementedFunctions = { 'ACOS': { method: 'acos', + parameters: [ + { argumentType: 'number' } + ], }, 'ASIN': { method: 'asin', + parameters: [ + { argumentType: 'number' } + ], }, 'COS': { method: 'cos', + parameters: [ + { argumentType: 'number' } + ], }, 'SIN': { method: 'sin', + parameters: [ + { argumentType: 'number' } + ], }, 'TAN': { method: 'tan', + parameters: [ + { argumentType: 'number' } + ], }, 'ATAN': { method: 'atan', + parameters: [ + { argumentType: 'number' } + ], }, 'ATAN2': { method: 'atan2', @@ -40,6 +58,9 @@ export class TrigonometryPlugin extends FunctionPlugin { }, 'COT': { method: 'ctg', + parameters: [ + { argumentType: 'number' } + ], }, } @@ -52,39 +73,27 @@ export class TrigonometryPlugin extends FunctionPlugin { * @param formulaAddress */ public acos(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, (coercedArg) => { - if (-1 <= coercedArg && coercedArg <= 1) { - return Math.acos(coercedArg) - } else { - return new CellError(ErrorType.NUM) - } - }) + return this.runFunction(ast.args, formulaAddress, TrigonometryPlugin.implementedFunctions.ACOS, Math.acos) } public asin(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, (coercedArg) => { - if (-1 <= coercedArg && coercedArg <= 1) { - return Math.asin(coercedArg) - } else { - return new CellError(ErrorType.NUM) - } - }) + return this.runFunction(ast.args, formulaAddress, TrigonometryPlugin.implementedFunctions.ASIN, Math.asin) } public cos(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, Math.cos) + return this.runFunction(ast.args, formulaAddress, TrigonometryPlugin.implementedFunctions.COS, Math.cos) } public sin(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, Math.sin) + return this.runFunction(ast.args, formulaAddress, TrigonometryPlugin.implementedFunctions.SIN, Math.sin) } public tan(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, Math.tan) + return this.runFunction(ast.args, formulaAddress, TrigonometryPlugin.implementedFunctions.TAN, Math.tan) } public atan(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, Math.atan) + return this.runFunction(ast.args, formulaAddress, TrigonometryPlugin.implementedFunctions.ATAN, Math.atan) } public atan2(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { @@ -92,7 +101,7 @@ export class TrigonometryPlugin extends FunctionPlugin { } public ctg(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithOneCoercedToNumberArgument(ast, formulaAddress, (coercedArg) => { + return this.runFunction(ast.args, formulaAddress, TrigonometryPlugin.implementedFunctions.COT, (coercedArg) => { if (coercedArg === 0) { return new CellError(ErrorType.DIV_BY_ZERO) } else { diff --git a/test/interpreter/function-log.spec.ts b/test/interpreter/function-log.spec.ts index 3c4e976518..b917cac613 100644 --- a/test/interpreter/function-log.spec.ts +++ b/test/interpreter/function-log.spec.ts @@ -43,6 +43,12 @@ describe('Function LOG', () => { expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NUM)) }) + it('for 1 base', () => { + const engine = HyperFormula.buildFromArray([['=LOG(42, 1)']]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NUM)) + }) + it('for negative base', () => { const engine = HyperFormula.buildFromArray([['=LOG(42, -42)']]) From 32acd0ba5d127c27c6771cf5c5b8f5dd61bed3c9 Mon Sep 17 00:00:00 2001 From: izulin Date: Sat, 25 Jul 2020 15:59:08 +0200 Subject: [PATCH 47/97] linter --- src/interpreter/plugin/CharPlugin.ts | 2 +- src/interpreter/plugin/CodePlugin.ts | 2 +- src/interpreter/plugin/DegreesPlugin.ts | 2 +- src/interpreter/plugin/ErrorFunctionPlugin.ts | 2 +- src/interpreter/plugin/LogarithmPlugin.ts | 2 +- src/interpreter/plugin/RoundingPlugin.ts | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/interpreter/plugin/CharPlugin.ts b/src/interpreter/plugin/CharPlugin.ts index e1f6e62c2e..47b60222bf 100644 --- a/src/interpreter/plugin/CharPlugin.ts +++ b/src/interpreter/plugin/CharPlugin.ts @@ -18,7 +18,7 @@ export class CharPlugin extends FunctionPlugin { } public char(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, CharPlugin.implementedFunctions.CHAR,(value: number) => { + return this.runFunction(ast.args, formulaAddress, CharPlugin.implementedFunctions.CHAR, (value: number) => { if (value < 1 || value > 255) { return new CellError(ErrorType.NUM) } diff --git a/src/interpreter/plugin/CodePlugin.ts b/src/interpreter/plugin/CodePlugin.ts index 9fbc7e7b4e..27974d6797 100644 --- a/src/interpreter/plugin/CodePlugin.ts +++ b/src/interpreter/plugin/CodePlugin.ts @@ -18,7 +18,7 @@ export class CodePlugin extends FunctionPlugin { } public code(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, CodePlugin.implementedFunctions.CODE,(value: string) => { + return this.runFunction(ast.args, formulaAddress, CodePlugin.implementedFunctions.CODE, (value: string) => { if (value.length === 0) { return new CellError(ErrorType.VALUE) } diff --git a/src/interpreter/plugin/DegreesPlugin.ts b/src/interpreter/plugin/DegreesPlugin.ts index 328b065b34..8c93d2b8be 100644 --- a/src/interpreter/plugin/DegreesPlugin.ts +++ b/src/interpreter/plugin/DegreesPlugin.ts @@ -18,7 +18,7 @@ export class DegreesPlugin extends FunctionPlugin { } public degrees(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, DegreesPlugin.implementedFunctions.DEGREES,(arg) => { + return this.runFunction(ast.args, formulaAddress, DegreesPlugin.implementedFunctions.DEGREES, (arg) => { return arg * (180 / Math.PI) }) } diff --git a/src/interpreter/plugin/ErrorFunctionPlugin.ts b/src/interpreter/plugin/ErrorFunctionPlugin.ts index b14f24cc49..5d597af37d 100644 --- a/src/interpreter/plugin/ErrorFunctionPlugin.ts +++ b/src/interpreter/plugin/ErrorFunctionPlugin.ts @@ -45,7 +45,7 @@ export class ErrorFunctionPlugin extends FunctionPlugin { } public erfc(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, ErrorFunctionPlugin.implementedFunctions.ERFC,erfc) + return this.runFunction(ast.args, formulaAddress, ErrorFunctionPlugin.implementedFunctions.ERFC, erfc) } } diff --git a/src/interpreter/plugin/LogarithmPlugin.ts b/src/interpreter/plugin/LogarithmPlugin.ts index 6154690806..ab44a05863 100644 --- a/src/interpreter/plugin/LogarithmPlugin.ts +++ b/src/interpreter/plugin/LogarithmPlugin.ts @@ -47,6 +47,6 @@ export class LogarithmPlugin extends FunctionPlugin { } public ln(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, LogarithmPlugin.implementedFunctions.LN,Math.log) + return this.runFunction(ast.args, formulaAddress, LogarithmPlugin.implementedFunctions.LN, Math.log) } } diff --git a/src/interpreter/plugin/RoundingPlugin.ts b/src/interpreter/plugin/RoundingPlugin.ts index a13c488798..12244e45f5 100644 --- a/src/interpreter/plugin/RoundingPlugin.ts +++ b/src/interpreter/plugin/RoundingPlugin.ts @@ -105,7 +105,7 @@ export class RoundingPlugin extends FunctionPlugin { } public even(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, RoundingPlugin.implementedFunctions.EVEN,(coercedNumberToRound) => { + return this.runFunction(ast.args, formulaAddress, RoundingPlugin.implementedFunctions.EVEN, (coercedNumberToRound) => { if (coercedNumberToRound < 0) { return -findNextEvenNumber(-coercedNumberToRound) } else { @@ -115,7 +115,7 @@ export class RoundingPlugin extends FunctionPlugin { } public odd(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, RoundingPlugin.implementedFunctions.ODD,(coercedNumberToRound) => { + return this.runFunction(ast.args, formulaAddress, RoundingPlugin.implementedFunctions.ODD, (coercedNumberToRound) => { if (coercedNumberToRound < 0) { return -findNextOddNumber(-coercedNumberToRound) } else { From c9550cbf495b38cdc5e92fea825f70bea2200645 Mon Sep 17 00:00:00 2001 From: izulin Date: Sat, 25 Jul 2020 17:02:30 +0200 Subject: [PATCH 48/97] . --- src/interpreter/plugin/FunctionPlugin.ts | 2 +- src/interpreter/plugin/RoundingPlugin.ts | 106 ++++++++--------------- 2 files changed, 35 insertions(+), 73 deletions(-) diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index bbff78d536..10bfdd6bb7 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -9,11 +9,11 @@ import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../. import {ColumnSearchStrategy} from '../../ColumnSearch/ColumnSearchStrategy' import {Config} from '../../Config' import {DependencyGraph} from '../../DependencyGraph' +import {Maybe} from '../../Maybe' import {Ast, AstNodeType, ProcedureAst} from '../../parser' import {coerceScalarToBoolean, coerceScalarToString} from '../ArithmeticHelper' import {Interpreter} from '../Interpreter' import {InterpreterValue, SimpleRangeValue} from '../InterpreterValue' -import {Maybe} from '../../Maybe' export interface ImplementedFunctions { [formulaId: string]: FunctionMetadata, diff --git a/src/interpreter/plugin/RoundingPlugin.ts b/src/interpreter/plugin/RoundingPlugin.ts index 12244e45f5..e01fce9c5f 100644 --- a/src/interpreter/plugin/RoundingPlugin.ts +++ b/src/interpreter/plugin/RoundingPlugin.ts @@ -24,15 +24,31 @@ export class RoundingPlugin extends FunctionPlugin { public static implementedFunctions = { 'ROUNDUP': { method: 'roundup', + parameters: [ + { argumentType: 'number' }, + { argumentType: 'number', defaultValue: 0}, + ], }, 'ROUNDDOWN': { method: 'rounddown', + parameters: [ + { argumentType: 'number' }, + { argumentType: 'number', defaultValue: 0}, + ], }, 'ROUND': { method: 'round', + parameters: [ + { argumentType: 'number' }, + { argumentType: 'number', defaultValue: 0}, + ], }, 'TRUNC': { method: 'trunc', + parameters: [ + { argumentType: 'number' }, + { argumentType: 'number', defaultValue: 0}, + ], }, 'INT': { method: 'intFunc', @@ -54,11 +70,16 @@ export class RoundingPlugin extends FunctionPlugin { }, 'CEILING': { method: 'ceiling', + parameters: [ + { argumentType: 'number' }, + { argumentType: 'number', defaultValue: 1 }, + { argumentType: 'number', defaultValue: 0 }, + ], }, } public roundup(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.commonArgumentsHandling2(ast, formulaAddress, (numberToRound: number, places: number): number => { + return this.runFunction(ast.args, formulaAddress, RoundingPlugin.implementedFunctions.ROUNDDOWN, (numberToRound: number, places: number): number => { const placesMultiplier = Math.pow(10, places) if (numberToRound < 0) { return -Math.ceil(-numberToRound * placesMultiplier) / placesMultiplier @@ -69,7 +90,7 @@ export class RoundingPlugin extends FunctionPlugin { } public rounddown(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.commonArgumentsHandling2(ast, formulaAddress, (numberToRound: number, places: number): number => { + return this.runFunction(ast.args, formulaAddress, RoundingPlugin.implementedFunctions.ROUNDDOWN, (numberToRound: number, places: number): number => { const placesMultiplier = Math.pow(10, places) if (numberToRound < 0) { return -Math.floor(-numberToRound * placesMultiplier) / placesMultiplier @@ -80,7 +101,7 @@ export class RoundingPlugin extends FunctionPlugin { } public round(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.commonArgumentsHandling2(ast, formulaAddress, (numberToRound: number, places: number): number => { + return this.runFunction(ast.args, formulaAddress, RoundingPlugin.implementedFunctions.ROUND, (numberToRound: number, places: number): number => { const placesMultiplier = Math.pow(10, places) if (numberToRound < 0) { return -Math.round(-numberToRound * placesMultiplier) / placesMultiplier @@ -125,79 +146,20 @@ export class RoundingPlugin extends FunctionPlugin { } public ceiling(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length < 1 || ast.args.length > 3) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - const value = this.getNumericArgument(ast, formulaAddress, 0) - if (value instanceof CellError) { - return value - } - - let significance: number | CellError = 1 - if (ast.args.length >= 2) { - significance = this.getNumericArgument(ast, formulaAddress, 1) - if (significance instanceof CellError) { - return significance + return this.runFunction(ast.args, formulaAddress, RoundingPlugin.implementedFunctions.CEILING,(value: number, significance: number, mode: number) => { + if (significance === 0 || value === 0) { + return 0 } - } - let mode: number | CellError = 0 - if (ast.args.length === 3) { - mode = this.getNumericArgument(ast, formulaAddress, 2) - if (mode instanceof CellError) { - return mode + if ((value > 0) !== (significance > 0) && ast.args.length > 1) { + return new CellError(ErrorType.NUM) } - } - - if (significance === 0 || value === 0) { - return 0 - } - - if ((value > 0) != (significance > 0) && ast.args.length > 1) { - return new CellError(ErrorType.NUM) - } - - if (mode === 0) { - significance = Math.abs(significance) - } - return Math.ceil(value / significance) * significance - } - - private commonArgumentsHandling2(ast: ProcedureAst, formulaAddress: SimpleCellAddress, roundingFunction: RoundingFunction): InternalScalarValue { - if (ast.args.length < 1 || ast.args.length > 2) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - const numberToRound = this.evaluateAst(ast.args[0], formulaAddress) - if (numberToRound instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - let coercedPlaces - if (ast.args[1]) { - const places = this.evaluateAst(ast.args[1], formulaAddress) - if (places instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) + if (mode === 0) { + significance = Math.abs(significance) } - coercedPlaces = this.coerceScalarToNumberOrError(places) - } else { - coercedPlaces = 0 - } - - const coercedNumberToRound = this.coerceScalarToNumberOrError(numberToRound) - if (coercedNumberToRound instanceof CellError) { - return coercedNumberToRound - } else if (coercedPlaces instanceof CellError) { - return coercedPlaces - } else { - return roundingFunction(coercedNumberToRound, coercedPlaces) - } + + return Math.ceil(value / significance) * significance + }) } } From c2439be55eb6e66abec7a66995856c850a4424ab Mon Sep 17 00:00:00 2001 From: izulin Date: Sat, 25 Jul 2020 17:17:59 +0200 Subject: [PATCH 49/97] . --- src/interpreter/plugin/DeltaPlugin.ts | 28 ++++++------------------ src/interpreter/plugin/FunctionPlugin.ts | 16 +++++++++----- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/src/interpreter/plugin/DeltaPlugin.ts b/src/interpreter/plugin/DeltaPlugin.ts index 43644ccb85..6cfbf84f95 100644 --- a/src/interpreter/plugin/DeltaPlugin.ts +++ b/src/interpreter/plugin/DeltaPlugin.ts @@ -11,30 +11,16 @@ export class DeltaPlugin extends FunctionPlugin { public static implementedFunctions = { 'DELTA': { method: 'delta', + parameters: [ + { argumentType: 'number' }, + { argumentType: 'number', defaultValue: 0 }, + ], }, } public delta(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length < 1 || ast.args.length > 2) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - const left = this.getNumericArgument(ast, formulaAddress, 0) - if (left instanceof CellError) { - return left - } - - let right: number | CellError = 0 - if (ast.args.length === 2) { - right = this.getNumericArgument(ast, formulaAddress, 1) - if (right instanceof CellError) { - return right - } - } - - return left === right ? 1 : 0 + return this.runFunction(ast.args, formulaAddress, DeltaPlugin.implementedFunctions.DELTA, (left: number, right: number) => { + return left === right ? 1 : 0 + }) } } diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 10bfdd6bb7..a60e9ba0ab 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -41,6 +41,8 @@ export interface FunctionArgumentDefinition { argumentType: string, defaultValue?: InternalScalarValue, softCoerce?: boolean, + minValue?: number, + maxValue?: number, } export type PluginFunctionType = (ast: ProcedureAst, formulaAddress: SimpleCellAddress) => InternalScalarValue @@ -146,17 +148,21 @@ export abstract class FunctionPlugin { public coerceScalarToNumberOrError = (arg: InternalScalarValue): number | CellError => this.interpreter.arithmeticHelper.coerceScalarToNumberOrError(arg) - public coerceToType(arg: InterpreterValue, coercedType: ArgumentTypes): Maybe { + public coerceToType(arg: InterpreterValue, coercedType: FunctionArgumentDefinition): Maybe { if(arg instanceof SimpleRangeValue) { - if(coercedType === 'range') { + if(coercedType.argumentType === 'range') { return arg } else { return undefined } } else { - switch (coercedType) { + switch (coercedType.argumentType as ArgumentTypes) { case 'number': - return this.coerceScalarToNumberOrError(arg) + const value = this.coerceScalarToNumberOrError(arg) + if (typeof value === 'number' && coercedType.maxValue !== undefined && coercedType.minValue !== undefined && (value < coercedType.minValue || value > coercedType.maxValue)) { + return new CellError(ErrorType.NUM) + } + return value case 'string': return coerceScalarToString(arg) case 'boolean': @@ -202,7 +208,7 @@ export abstract class FunctionPlugin { if(arg === undefined) { return new CellError(ErrorType.NA) } - const coercedArg = this.coerceToType(arg, argumentDefinitions[j].argumentType as ArgumentTypes) + const coercedArg = this.coerceToType(arg, argumentDefinitions[j]) if(coercedArg === undefined && !argumentDefinitions[j].softCoerce) { argCoerceFailure = argCoerceFailure ?? (new CellError(ErrorType.VALUE)) } From 5fbd9d7a6dde35bf169c1e467c61174f16c2ba6e Mon Sep 17 00:00:00 2001 From: izulin Date: Sat, 25 Jul 2020 17:24:02 +0200 Subject: [PATCH 50/97] linter --- src/interpreter/plugin/FunctionPlugin.ts | 1 + src/interpreter/plugin/RoundingPlugin.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index a60e9ba0ab..5176af501a 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -158,6 +158,7 @@ export abstract class FunctionPlugin { } else { switch (coercedType.argumentType as ArgumentTypes) { case 'number': + // eslint-disable-next-line no-case-declarations const value = this.coerceScalarToNumberOrError(arg) if (typeof value === 'number' && coercedType.maxValue !== undefined && coercedType.minValue !== undefined && (value < coercedType.minValue || value > coercedType.maxValue)) { return new CellError(ErrorType.NUM) diff --git a/src/interpreter/plugin/RoundingPlugin.ts b/src/interpreter/plugin/RoundingPlugin.ts index e01fce9c5f..18eba40037 100644 --- a/src/interpreter/plugin/RoundingPlugin.ts +++ b/src/interpreter/plugin/RoundingPlugin.ts @@ -146,7 +146,7 @@ export class RoundingPlugin extends FunctionPlugin { } public ceiling(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, RoundingPlugin.implementedFunctions.CEILING,(value: number, significance: number, mode: number) => { + return this.runFunction(ast.args, formulaAddress, RoundingPlugin.implementedFunctions.CEILING, (value: number, significance: number, mode: number) => { if (significance === 0 || value === 0) { return 0 } From cdb09385fef40e4334ab283e1ec4f6764eb3b720 Mon Sep 17 00:00:00 2001 From: izulin Date: Sun, 26 Jul 2020 13:40:28 +0200 Subject: [PATCH 51/97] more --- .../plugin/BitwiseLogicOperationsPlugin.ts | 33 +++++++++---------- src/interpreter/plugin/ErrorFunctionPlugin.ts | 30 ++++++----------- src/interpreter/plugin/FunctionPlugin.ts | 16 +++++++-- 3 files changed, 38 insertions(+), 41 deletions(-) diff --git a/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts b/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts index 0cfe193525..a83028ed10 100644 --- a/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts +++ b/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts @@ -11,45 +11,42 @@ export class BitwiseLogicOperationsPlugin extends FunctionPlugin { public static implementedFunctions = { 'BITAND': { method: 'bitand', + parameters: [ + { argumentType: 'integer', minValue: 0 }, + { argumentType: 'integer', minValue: 0 }, + ] }, 'BITOR': { method: 'bitor', + parameters: [ + { argumentType: 'integer', minValue: 0 }, + { argumentType: 'integer', minValue: 0 }, + ] }, 'BITXOR': { method: 'bitxor', + parameters: [ + { argumentType: 'integer', minValue: 0 }, + { argumentType: 'integer', minValue: 0 }, + ] }, } public bitand(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithTwoPositiveIntegerArguments(ast, formulaAddress, (left: number, right: number) => { + return this.runFunction(ast.args, formulaAddress, BitwiseLogicOperationsPlugin.implementedFunctions.BITAND, (left: number, right: number) => { return left & right }) } public bitor(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithTwoPositiveIntegerArguments(ast, formulaAddress, (left: number, right: number) => { + return this.runFunction(ast.args, formulaAddress, BitwiseLogicOperationsPlugin.implementedFunctions.BITOR, (left: number, right: number) => { return left | right }) } public bitxor(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.templateWithTwoPositiveIntegerArguments(ast, formulaAddress, (left: number, right: number) => { + return this.runFunction(ast.args, formulaAddress, BitwiseLogicOperationsPlugin.implementedFunctions.BITXOR, (left: number, right: number) => { return left ^ right }) } - - private templateWithTwoPositiveIntegerArguments(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (left: number, right: number) => InternalScalarValue): InternalScalarValue { - const validationResult = this.validateTwoNumericArguments(ast, formulaAddress) - - if (validationResult instanceof CellError) { - return validationResult - } - - const [coercedLeft, coercedRight] = validationResult - if (coercedLeft < 0 || coercedRight < 0 || !Number.isInteger(coercedLeft) || !Number.isInteger(coercedRight)) { - return new CellError(ErrorType.NUM) - } - - return fn(coercedLeft, coercedRight) - } } diff --git a/src/interpreter/plugin/ErrorFunctionPlugin.ts b/src/interpreter/plugin/ErrorFunctionPlugin.ts index 5d597af37d..2ad9d40216 100644 --- a/src/interpreter/plugin/ErrorFunctionPlugin.ts +++ b/src/interpreter/plugin/ErrorFunctionPlugin.ts @@ -3,7 +3,8 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' +import {CellError, EmptyValue, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' +import {upperBound} from '../../ColumnSearch/ColumnIndex' import {AstNodeType, ProcedureAst} from '../../parser' import {FunctionPlugin} from './FunctionPlugin' @@ -11,6 +12,10 @@ export class ErrorFunctionPlugin extends FunctionPlugin { public static implementedFunctions = { 'ERF': { method: 'erf', + parameters: [ + { argumentType: 'number' }, + { argumentType: 'number', defaultValue: EmptyValue as InternalScalarValue}, //hacking around since ERF can take 1 or 2 args + ], }, 'ERFC': { method: 'erfc', @@ -21,27 +26,12 @@ export class ErrorFunctionPlugin extends FunctionPlugin { } public erf(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length < 1 || ast.args.length > 2) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - const lowerBound = this.getNumericArgument(ast, formulaAddress, 0) - if (lowerBound instanceof CellError) { - return lowerBound - } - - if (ast.args.length === 2) { - const upperBound = this.getNumericArgument(ast, formulaAddress, 1) - if (upperBound instanceof CellError) { - return upperBound + return this.runFunction(ast.args, formulaAddress, ErrorFunctionPlugin.implementedFunctions.ERF, (lowerBound, upperBound) => { + if(ast.args.length === 1) { + return erf(lowerBound) } return erf2(lowerBound, upperBound) - } - - return erf(lowerBound) + }) } public erfc(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 5176af501a..612ea79faf 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -35,7 +35,7 @@ export interface FunctionPluginDefinition { implementedFunctions: ImplementedFunctions, } -export type ArgumentTypes = 'string' | 'number' | 'boolean' | 'scalar' | 'noerror' | 'range' +export type ArgumentTypes = 'string' | 'number' | 'boolean' | 'scalar' | 'noerror' | 'range' | 'integer' export interface FunctionArgumentDefinition { argumentType: string, @@ -157,10 +157,20 @@ export abstract class FunctionPlugin { } } else { switch (coercedType.argumentType as ArgumentTypes) { + case 'integer': case 'number': // eslint-disable-next-line no-case-declarations const value = this.coerceScalarToNumberOrError(arg) - if (typeof value === 'number' && coercedType.maxValue !== undefined && coercedType.minValue !== undefined && (value < coercedType.minValue || value > coercedType.maxValue)) { + if(typeof value !== 'number') { + return value + } + if(coercedType.maxValue !== undefined && value > coercedType.maxValue) { + return new CellError(ErrorType.NUM) + } + if (coercedType.minValue !== undefined && value < coercedType.minValue) { + return new CellError(ErrorType.NUM) + } + if(coercedType.argumentType === 'integer' && !Number.isInteger(value)) { return new CellError(ErrorType.NUM) } return value @@ -209,7 +219,7 @@ export abstract class FunctionPlugin { if(arg === undefined) { return new CellError(ErrorType.NA) } - const coercedArg = this.coerceToType(arg, argumentDefinitions[j]) + const coercedArg = scalarValues[i] !== undefined ? this.coerceToType(arg, argumentDefinitions[j]) : arg if(coercedArg === undefined && !argumentDefinitions[j].softCoerce) { argCoerceFailure = argCoerceFailure ?? (new CellError(ErrorType.VALUE)) } From 9883e4df39f79e4a0085c969471796085aaeeb77 Mon Sep 17 00:00:00 2001 From: izulin Date: Sun, 26 Jul 2020 17:08:55 +0200 Subject: [PATCH 52/97] defaults --- src/interpreter/plugin/BitShiftPlugin.ts | 54 ++++++++----------- .../plugin/BitwiseLogicOperationsPlugin.ts | 18 +++---- src/interpreter/plugin/ErrorFunctionPlugin.ts | 11 ++-- src/interpreter/plugin/FunctionPlugin.ts | 41 ++++---------- 4 files changed, 45 insertions(+), 79 deletions(-) diff --git a/src/interpreter/plugin/BitShiftPlugin.ts b/src/interpreter/plugin/BitShiftPlugin.ts index 1d8b20812c..445dd87e7e 100644 --- a/src/interpreter/plugin/BitShiftPlugin.ts +++ b/src/interpreter/plugin/BitShiftPlugin.ts @@ -15,59 +15,49 @@ export class BitShiftPlugin extends FunctionPlugin { public static implementedFunctions = { 'BITLSHIFT': { method: 'bitlshift', + parameters: [ + { argumentType: 'integer', minValue: 0 }, + { argumentType: 'integer', minValue: SHIFT_MIN_POSITIONS, maxValue: SHIFT_MAX_POSITIONS }, + ] }, 'BITRSHIFT': { method: 'bitrshift', + parameters: [ + { argumentType: 'integer', minValue: 0 }, + { argumentType: 'integer', minValue: SHIFT_MIN_POSITIONS, maxValue: SHIFT_MAX_POSITIONS }, + ] }, } public bitlshift(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.bitshiftTemplate(ast, formulaAddress, shiftLeft) + return this.runFunction(ast.args, formulaAddress, BitShiftPlugin.implementedFunctions.BITLSHIFT, shiftLeft) } public bitrshift(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.bitshiftTemplate(ast, formulaAddress, shiftRight) - } - - private bitshiftTemplate(ast: ProcedureAst, formulaAddress: SimpleCellAddress, fn: (value: number, positions: number) => number): InternalScalarValue { - const validationResult = this.validateTwoNumericArguments(ast, formulaAddress) - - if (validationResult instanceof CellError) { - return validationResult - } - - const [coercedValue, coercedPositions] = validationResult - - if (coercedValue < 0 || !Number.isInteger(coercedValue) || !Number.isInteger(coercedPositions)) { - return new CellError(ErrorType.NUM) - } - - if (coercedPositions < SHIFT_MIN_POSITIONS || coercedPositions > SHIFT_MAX_POSITIONS) { - return new CellError(ErrorType.NUM) - } - - const result = fn(coercedValue, coercedPositions) - - if (result > MAX_48BIT_INTEGER) { - return new CellError(ErrorType.NUM) - } else { - return result - } + return this.runFunction(ast.args, formulaAddress, BitShiftPlugin.implementedFunctions.BITRSHIFT, shiftRight) } } -function shiftLeft(value: number, positions: number): number { +function shiftLeft(value: number, positions: number): number | CellError { if (positions < 0) { return shiftRight(value, -positions) } else { - return value * Math.pow(2, positions) + return validate(value * Math.pow(2, positions)) } } -function shiftRight(value: number, positions: number): number { +function shiftRight(value: number, positions: number): number | CellError { if (positions < 0) { return shiftLeft(value, -positions) } else { - return Math.floor(value / Math.pow(2, positions)) + return validate(Math.floor(value / Math.pow(2, positions))) + } +} + +function validate(result: number): number | CellError { + if (result > MAX_48BIT_INTEGER) { + return new CellError(ErrorType.NUM) + } else { + return result } } diff --git a/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts b/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts index a83028ed10..c4bf18c3ff 100644 --- a/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts +++ b/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts @@ -33,20 +33,20 @@ export class BitwiseLogicOperationsPlugin extends FunctionPlugin { } public bitand(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, BitwiseLogicOperationsPlugin.implementedFunctions.BITAND, (left: number, right: number) => { - return left & right - }) + return this.runFunction(ast.args, formulaAddress, BitwiseLogicOperationsPlugin.implementedFunctions.BITAND, + (left: number, right: number) => left & right + ) } public bitor(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, BitwiseLogicOperationsPlugin.implementedFunctions.BITOR, (left: number, right: number) => { - return left | right - }) + return this.runFunction(ast.args, formulaAddress, BitwiseLogicOperationsPlugin.implementedFunctions.BITOR, + (left: number, right: number) => left | right + ) } public bitxor(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, BitwiseLogicOperationsPlugin.implementedFunctions.BITXOR, (left: number, right: number) => { - return left ^ right - }) + return this.runFunction(ast.args, formulaAddress, BitwiseLogicOperationsPlugin.implementedFunctions.BITXOR, + (left: number, right: number) => left ^ right + ) } } diff --git a/src/interpreter/plugin/ErrorFunctionPlugin.ts b/src/interpreter/plugin/ErrorFunctionPlugin.ts index 2ad9d40216..9ce3f6c85d 100644 --- a/src/interpreter/plugin/ErrorFunctionPlugin.ts +++ b/src/interpreter/plugin/ErrorFunctionPlugin.ts @@ -14,7 +14,7 @@ export class ErrorFunctionPlugin extends FunctionPlugin { method: 'erf', parameters: [ { argumentType: 'number' }, - { argumentType: 'number', defaultValue: EmptyValue as InternalScalarValue}, //hacking around since ERF can take 1 or 2 args + { argumentType: 'number', optionalArg: true}, ], }, 'ERFC': { @@ -27,10 +27,11 @@ export class ErrorFunctionPlugin extends FunctionPlugin { public erf(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { return this.runFunction(ast.args, formulaAddress, ErrorFunctionPlugin.implementedFunctions.ERF, (lowerBound, upperBound) => { - if(ast.args.length === 1) { + if(upperBound===undefined) { return erf(lowerBound) + } else { + return erf(upperBound) - erf(lowerBound) } - return erf2(lowerBound, upperBound) }) } @@ -65,10 +66,6 @@ function erfApprox(x: number): number { return 1 - (1 / Math.pow(poly, polyExponent)) } -function erf2(lowerBound: number, upperBound: number): number { - return erf(upperBound) - erf(lowerBound) -} - function erfc(x: number): number { return 1 - erf(x) } diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 612ea79faf..d114c23cf2 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -40,7 +40,8 @@ export type ArgumentTypes = 'string' | 'number' | 'boolean' | 'scalar' | 'noerro export interface FunctionArgumentDefinition { argumentType: string, defaultValue?: InternalScalarValue, - softCoerce?: boolean, + softCoerce?: boolean, //failed coerce makes function ignore the arg instead of producing error + optionalArg?: boolean, // minValue?: number, maxValue?: number, } @@ -96,35 +97,6 @@ export abstract class FunctionPlugin { return values } - protected validateTwoNumericArguments(ast: ProcedureAst, formulaAddress: SimpleCellAddress): [number, number] | CellError { - if (ast.args.length !== 2) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - const left = this.evaluateAst(ast.args[0], formulaAddress) - if (left instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - const coercedLeft = this.coerceScalarToNumberOrError(left) - if (coercedLeft instanceof CellError) { - return coercedLeft - } - - const right = this.evaluateAst(ast.args[1], formulaAddress) - if (right instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - const coercedRight = this.coerceScalarToNumberOrError(right) - if (coercedRight instanceof CellError) { - return coercedRight - } - - return [coercedLeft, coercedRight] - } - protected getNumericArgument(ast: ProcedureAst, formulaAddress: SimpleCellAddress, position: number, min?: number, max?: number): number | CellError { if (position > ast.args.length - 1) { return new CellError(ErrorType.NA) @@ -217,7 +189,14 @@ export abstract class FunctionPlugin { } const arg = scalarValues[i] ?? argumentDefinitions[j]?.defaultValue if(arg === undefined) { - return new CellError(ErrorType.NA) + if(argumentDefinitions[j]?.optionalArg) { + i++ + j++ + coercedArguments.push(undefined) + continue + } else { + return new CellError(ErrorType.NA) + } } const coercedArg = scalarValues[i] !== undefined ? this.coerceToType(arg, argumentDefinitions[j]) : arg if(coercedArg === undefined && !argumentDefinitions[j].softCoerce) { From 82e0b1ebedd21f38c6b48001f51690d788a353e7 Mon Sep 17 00:00:00 2001 From: izulin Date: Mon, 27 Jul 2020 12:41:00 +0200 Subject: [PATCH 53/97] . --- .../plugin/BitwiseLogicOperationsPlugin.ts | 2 +- src/interpreter/plugin/CountUniquePlugin.ts | 2 +- src/interpreter/plugin/DegreesPlugin.ts | 6 +- src/interpreter/plugin/DeltaPlugin.ts | 10 +- src/interpreter/plugin/ErrorFunctionPlugin.ts | 5 +- src/interpreter/plugin/FinancialPlugin.ts | 5 +- src/interpreter/plugin/FunctionPlugin.ts | 31 +-- src/interpreter/plugin/IsEvenPlugin.ts | 29 +-- src/interpreter/plugin/IsOddPlugin.ts | 28 +-- src/interpreter/plugin/LogarithmPlugin.ts | 19 +- src/interpreter/plugin/MathConstantsPlugin.ts | 28 +-- .../plugin/RadixConversionPlugin.ts | 220 +++++++----------- 12 files changed, 142 insertions(+), 243 deletions(-) diff --git a/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts b/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts index c4bf18c3ff..0be06e94c1 100644 --- a/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts +++ b/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts @@ -3,7 +3,7 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' +import {InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' import {FunctionPlugin} from './FunctionPlugin' diff --git a/src/interpreter/plugin/CountUniquePlugin.ts b/src/interpreter/plugin/CountUniquePlugin.ts index 8dac701166..32569ad9bb 100644 --- a/src/interpreter/plugin/CountUniquePlugin.ts +++ b/src/interpreter/plugin/CountUniquePlugin.ts @@ -4,7 +4,7 @@ */ import {CellError, EmptyValueType, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' -import {AstNodeType, ProcedureAst} from '../../parser' +import {ProcedureAst} from '../../parser' import {FunctionPlugin} from './FunctionPlugin' /** diff --git a/src/interpreter/plugin/DegreesPlugin.ts b/src/interpreter/plugin/DegreesPlugin.ts index 8c93d2b8be..8a08df9d19 100644 --- a/src/interpreter/plugin/DegreesPlugin.ts +++ b/src/interpreter/plugin/DegreesPlugin.ts @@ -18,8 +18,8 @@ export class DegreesPlugin extends FunctionPlugin { } public degrees(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, DegreesPlugin.implementedFunctions.DEGREES, (arg) => { - return arg * (180 / Math.PI) - }) + return this.runFunction(ast.args, formulaAddress, DegreesPlugin.implementedFunctions.DEGREES, + (arg) => arg * (180 / Math.PI) + ) } } diff --git a/src/interpreter/plugin/DeltaPlugin.ts b/src/interpreter/plugin/DeltaPlugin.ts index 6cfbf84f95..631f280c4f 100644 --- a/src/interpreter/plugin/DeltaPlugin.ts +++ b/src/interpreter/plugin/DeltaPlugin.ts @@ -3,8 +3,8 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' -import {AstNodeType, ProcedureAst} from '../../parser' +import {InternalScalarValue, SimpleCellAddress} from '../../Cell' +import {ProcedureAst} from '../../parser' import {FunctionPlugin} from './FunctionPlugin' export class DeltaPlugin extends FunctionPlugin { @@ -19,8 +19,8 @@ export class DeltaPlugin extends FunctionPlugin { } public delta(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, DeltaPlugin.implementedFunctions.DELTA, (left: number, right: number) => { - return left === right ? 1 : 0 - }) + return this.runFunction(ast.args, formulaAddress, DeltaPlugin.implementedFunctions.DELTA, + (left: number, right: number) => (left === right ? 1 : 0) + ) } } diff --git a/src/interpreter/plugin/ErrorFunctionPlugin.ts b/src/interpreter/plugin/ErrorFunctionPlugin.ts index 9ce3f6c85d..1afbdb682c 100644 --- a/src/interpreter/plugin/ErrorFunctionPlugin.ts +++ b/src/interpreter/plugin/ErrorFunctionPlugin.ts @@ -3,9 +3,8 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {CellError, EmptyValue, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' -import {upperBound} from '../../ColumnSearch/ColumnIndex' -import {AstNodeType, ProcedureAst} from '../../parser' +import {InternalScalarValue, SimpleCellAddress} from '../../Cell' +import {ProcedureAst} from '../../parser' import {FunctionPlugin} from './FunctionPlugin' export class ErrorFunctionPlugin extends FunctionPlugin { diff --git a/src/interpreter/plugin/FinancialPlugin.ts b/src/interpreter/plugin/FinancialPlugin.ts index e6a26620d7..1c904b3ef7 100644 --- a/src/interpreter/plugin/FinancialPlugin.ts +++ b/src/interpreter/plugin/FinancialPlugin.ts @@ -3,9 +3,8 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' -import {AstNodeType, ProcedureAst} from '../../parser' -import {SimpleRangeValue} from '../InterpreterValue' +import {InternalScalarValue, SimpleCellAddress} from '../../Cell' +import {ProcedureAst} from '../../parser' import {FunctionPlugin} from './FunctionPlugin' export class FinancialPlugin extends FunctionPlugin { diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index d114c23cf2..3601e0633e 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -10,7 +10,7 @@ import {ColumnSearchStrategy} from '../../ColumnSearch/ColumnSearchStrategy' import {Config} from '../../Config' import {DependencyGraph} from '../../DependencyGraph' import {Maybe} from '../../Maybe' -import {Ast, AstNodeType, ProcedureAst} from '../../parser' +import {Ast, ProcedureAst} from '../../parser' import {coerceScalarToBoolean, coerceScalarToString} from '../ArithmeticHelper' import {Interpreter} from '../Interpreter' import {InterpreterValue, SimpleRangeValue} from '../InterpreterValue' @@ -44,6 +44,8 @@ export interface FunctionArgumentDefinition { optionalArg?: boolean, // minValue?: number, maxValue?: number, + lessThan?: number, + greaterThan?: number, } export type PluginFunctionType = (ast: ProcedureAst, formulaAddress: SimpleCellAddress) => InternalScalarValue @@ -97,27 +99,6 @@ export abstract class FunctionPlugin { return values } - protected getNumericArgument(ast: ProcedureAst, formulaAddress: SimpleCellAddress, position: number, min?: number, max?: number): number | CellError { - if (position > ast.args.length - 1) { - return new CellError(ErrorType.NA) - } - if (ast.args[position].type === AstNodeType.EMPTY) { - return new CellError(ErrorType.NUM) - } - const arg = this.evaluateAst(ast.args[position]!, formulaAddress) - - if (arg instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - const value = this.coerceScalarToNumberOrError(arg) - if (typeof value === 'number' && min !== undefined && max !== undefined && (value < min || value > max)) { - return new CellError(ErrorType.NUM) - } - - return value - } - public coerceScalarToNumberOrError = (arg: InternalScalarValue): number | CellError => this.interpreter.arithmeticHelper.coerceScalarToNumberOrError(arg) public coerceToType(arg: InterpreterValue, coercedType: FunctionArgumentDefinition): Maybe { @@ -142,6 +123,12 @@ export abstract class FunctionPlugin { if (coercedType.minValue !== undefined && value < coercedType.minValue) { return new CellError(ErrorType.NUM) } + if(coercedType.lessThan !== undefined && value >= coercedType.lessThan) { + return new CellError(ErrorType.NUM) + } + if (coercedType.greaterThan !== undefined && value <= coercedType.greaterThan) { + return new CellError(ErrorType.NUM) + } if(coercedType.argumentType === 'integer' && !Number.isInteger(value)) { return new CellError(ErrorType.NUM) } diff --git a/src/interpreter/plugin/IsEvenPlugin.ts b/src/interpreter/plugin/IsEvenPlugin.ts index 0a12b424d9..37bfd85cc5 100644 --- a/src/interpreter/plugin/IsEvenPlugin.ts +++ b/src/interpreter/plugin/IsEvenPlugin.ts @@ -3,36 +3,23 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' -import {AstNodeType, ProcedureAst} from '../../parser' -import {SimpleRangeValue} from '../InterpreterValue' +import {InternalScalarValue, SimpleCellAddress} from '../../Cell' +import {ProcedureAst} from '../../parser' import {FunctionPlugin} from './FunctionPlugin' export class IsEvenPlugin extends FunctionPlugin { public static implementedFunctions = { 'ISEVEN': { method: 'iseven', + parameters: [ + { argumentType: 'number'} + ] }, } public iseven(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length != 1) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - const arg = this.evaluateAst(ast.args[0], formulaAddress) - if (arg instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - const coercedValue = this.coerceScalarToNumberOrError(arg) - if (coercedValue instanceof CellError) { - return coercedValue - } else { - return (coercedValue % 2 === 0) - } - + return this.runFunction(ast.args, formulaAddress, IsEvenPlugin.implementedFunctions.ISEVEN, + (val) => (val % 2 === 0) + ) } } diff --git a/src/interpreter/plugin/IsOddPlugin.ts b/src/interpreter/plugin/IsOddPlugin.ts index 6d45b4ff00..b83e621d02 100644 --- a/src/interpreter/plugin/IsOddPlugin.ts +++ b/src/interpreter/plugin/IsOddPlugin.ts @@ -3,35 +3,23 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' -import {AstNodeType, ProcedureAst} from '../../parser' -import {SimpleRangeValue} from '../InterpreterValue' +import {InternalScalarValue, SimpleCellAddress} from '../../Cell' +import {ProcedureAst} from '../../parser' import {FunctionPlugin} from './FunctionPlugin' export class IsOddPlugin extends FunctionPlugin { public static implementedFunctions = { 'ISODD': { method: 'isodd', + parameters: [ + { argumentType: 'number'} + ] }, } public isodd(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length != 1) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - const arg = this.evaluateAst(ast.args[0], formulaAddress) - if (arg instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - const coercedValue = this.coerceScalarToNumberOrError(arg) - if (coercedValue instanceof CellError) { - return coercedValue - } else { - return (coercedValue % 2 === 1) - } + return this.runFunction(ast.args, formulaAddress, IsOddPlugin.implementedFunctions.ISODD, + (val) => (val % 2 === 1) + ) } } diff --git a/src/interpreter/plugin/LogarithmPlugin.ts b/src/interpreter/plugin/LogarithmPlugin.ts index ab44a05863..0884a1128c 100644 --- a/src/interpreter/plugin/LogarithmPlugin.ts +++ b/src/interpreter/plugin/LogarithmPlugin.ts @@ -3,9 +3,8 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' -import {AstNodeType, ProcedureAst} from '../../parser' -import {SimpleRangeValue} from '../InterpreterValue' +import {InternalScalarValue, SimpleCellAddress} from '../../Cell' +import {ProcedureAst} from '../../parser' import {FunctionPlugin} from './FunctionPlugin' export class LogarithmPlugin extends FunctionPlugin { @@ -20,8 +19,8 @@ export class LogarithmPlugin extends FunctionPlugin { 'LOG': { method: 'log', parameters: [ - { argumentType: 'number' }, - { argumentType: 'number', defaultValue: 10 }, + { argumentType: 'number', greaterThan: 0 }, + { argumentType: 'number', defaultValue: 10, greaterThan: 0 }, ], }, 'LN': { @@ -37,13 +36,9 @@ export class LogarithmPlugin extends FunctionPlugin { } public log(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, LogarithmPlugin.implementedFunctions.LOG, (arg: number, base: number) => { - if(arg > 0 && base > 0) { - return Math.log(arg) / Math.log(base) - } else { - return new CellError(ErrorType.NUM) - } - }) + return this.runFunction(ast.args, formulaAddress, LogarithmPlugin.implementedFunctions.LOG, + (arg: number, base: number) => Math.log(arg) / Math.log(base) + ) } public ln(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { diff --git a/src/interpreter/plugin/MathConstantsPlugin.ts b/src/interpreter/plugin/MathConstantsPlugin.ts index 80e1bfc9b9..bafc1a460b 100644 --- a/src/interpreter/plugin/MathConstantsPlugin.ts +++ b/src/interpreter/plugin/MathConstantsPlugin.ts @@ -3,8 +3,8 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' -import {AstNodeType, ProcedureAst} from '../../parser' +import {InternalScalarValue, SimpleCellAddress} from '../../Cell' +import {ProcedureAst} from '../../parser' import {FunctionPlugin} from './FunctionPlugin' const PI = parseFloat(Math.PI.toFixed(14)) @@ -14,31 +14,23 @@ export class MathConstantsPlugin extends FunctionPlugin { public static implementedFunctions = { 'PI': { method: 'pi', + parameters: [], }, 'E': { method: 'e', + parameters: [], }, } - // eslint-disable-next-line @typescript-eslint/no-unused-vars public pi(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length > 0) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - return PI + return this.runFunction(ast.args, formulaAddress, MathConstantsPlugin.implementedFunctions.PI, + () => PI + ) } - // eslint-disable-next-line @typescript-eslint/no-unused-vars public e(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length > 0) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - return E + return this.runFunction(ast.args, formulaAddress, MathConstantsPlugin.implementedFunctions.E, + () => E + ) } } diff --git a/src/interpreter/plugin/RadixConversionPlugin.ts b/src/interpreter/plugin/RadixConversionPlugin.ts index ad5fd62663..7ddb7075ff 100644 --- a/src/interpreter/plugin/RadixConversionPlugin.ts +++ b/src/interpreter/plugin/RadixConversionPlugin.ts @@ -5,9 +5,8 @@ import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {padLeft} from '../../format/format' -import {AstNodeType, ProcedureAst} from '../../parser' -import {coerceScalarToString} from '../ArithmeticHelper' -import {SimpleRangeValue} from '../InterpreterValue' +import {Maybe} from '../../Maybe' +import {ProcedureAst} from '../../parser' import {FunctionPlugin} from './FunctionPlugin' const NUMBER_OF_BITS = 10 @@ -20,187 +19,132 @@ export class RadixConversionPlugin extends FunctionPlugin { public static implementedFunctions = { 'DEC2BIN': { method: 'dec2bin', + parameters: [ + { argumentType: 'number', minValue: minValFromBase(2), maxValue: maxValFromBase(2) }, + { argumentType: 'number', optionalArg: true, minValue: 1, maxValue: 10 }, + ], }, 'DEC2OCT': { method: 'dec2oct', + parameters: [ + { argumentType: 'number', minValue: minValFromBase(8), maxValue: maxValFromBase(8) }, + { argumentType: 'number', optionalArg: true, minValue: 1, maxValue: 10 }, + ], }, 'DEC2HEX': { method: 'dec2hex', + parameters: [ + { argumentType: 'number', minValue: minValFromBase(16), maxValue: maxValFromBase(16) }, + { argumentType: 'number', optionalArg: true, minValue: 1, maxValue: 10 }, + ], }, 'BIN2DEC': { method: 'bin2dec', + parameters: [ + { argumentType: 'string' } + ], }, 'BIN2OCT': { method: 'bin2oct', + parameters: [ + { argumentType: 'string' }, + { argumentType: 'number', optionalArg: true, minValue: 0, maxValue: DECIMAL_NUMBER_OF_BITS}, + ], }, 'BIN2HEX': { method: 'bin2hex', + parameters: [ + { argumentType: 'string' }, + { argumentType: 'number', optionalArg: true, minValue: 0, maxValue: DECIMAL_NUMBER_OF_BITS}, + ], }, 'DECIMAL': { method: 'decimal', + parameters: [ + { argumentType: 'string' }, + { argumentType: 'number', minValue: MIN_BASE, maxValue: MAX_BASE }, + ], }, 'BASE': { method: 'base', + parameters: [ + { argumentType: 'number', minValue: 0 }, + { argumentType: 'number', minValue: MIN_BASE, maxValue: MAX_BASE }, + { argumentType: 'number', optionalArg: true, minValue: 0, maxValue: DECIMAL_NUMBER_OF_BITS}, + ], }, } public dec2bin(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.dec2base(ast, formulaAddress, 2) + return this.runFunction(ast.args, formulaAddress, RadixConversionPlugin.implementedFunctions.DEC2BIN, + (value, places) => decimalToBaseWithExactPadding(value, 2, places) + ) } public dec2oct(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.dec2base(ast, formulaAddress, 8) + return this.runFunction(ast.args, formulaAddress, RadixConversionPlugin.implementedFunctions.DEC2OCT, + (value, places) => decimalToBaseWithExactPadding(value, 8, places) + ) } public dec2hex(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.dec2base(ast, formulaAddress, 16) + return this.runFunction(ast.args, formulaAddress, RadixConversionPlugin.implementedFunctions.DEC2HEX, + (value, places) => decimalToBaseWithExactPadding(value, 16, places) + ) } public bin2dec(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length !== 1) { - return new CellError(ErrorType.NA) - } - - const binaryWithSign = this.getFirstArgumentAsNumberInBase(ast, formulaAddress, 2, NUMBER_OF_BITS) - if (binaryWithSign instanceof CellError) { - return binaryWithSign - } - - return twoComplementToDecimal(binaryWithSign) + return this.runFunction(ast.args, formulaAddress, RadixConversionPlugin.implementedFunctions.BIN2DEC, (binary) => { + const binaryWithSign = coerceStringToBase(binary, 2, NUMBER_OF_BITS) + if(binaryWithSign === undefined) { + return new CellError(ErrorType.NUM) + } + return twoComplementToDecimal(binaryWithSign) + }) } public bin2oct(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.bin2base(ast, formulaAddress, 8) + return this.runFunction(ast.args, formulaAddress, RadixConversionPlugin.implementedFunctions.BIN2OCT, (binary, places) => { + const binaryWithSign = coerceStringToBase(binary, 2, NUMBER_OF_BITS) + if(binaryWithSign === undefined) { + return new CellError(ErrorType.NUM) + } + return decimalToBaseWithExactPadding(twoComplementToDecimal(binaryWithSign), 8, places) + }) } public bin2hex(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.bin2base(ast, formulaAddress, 16) + return this.runFunction(ast.args, formulaAddress, RadixConversionPlugin.implementedFunctions.BIN2HEX, (binary, places) => { + const binaryWithSign = coerceStringToBase(binary, 2, NUMBER_OF_BITS) + if(binaryWithSign === undefined) { + return new CellError(ErrorType.NUM) + } + return decimalToBaseWithExactPadding(twoComplementToDecimal(binaryWithSign), 16, places) + }) } public base(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length < 2 || ast.args.length > 3) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - const value = this.getNumericArgument(ast, formulaAddress, 0) - if (value instanceof CellError) { - return value - } - - const base = this.getNumericArgument(ast, formulaAddress, 1, MIN_BASE, MAX_BASE) - if (base instanceof CellError) { - return base - } - - let padding - if (ast.args.length === 3) { - padding = this.getNumericArgument(ast, formulaAddress, 2, 0, DECIMAL_NUMBER_OF_BITS) - if (padding instanceof CellError) { - return padding - } - } - - if (value < 0) { - return new CellError(ErrorType.NUM) - } - - return decimalToBaseWithMinimumPadding(value, base, padding) + return this.runFunction(ast.args, formulaAddress, RadixConversionPlugin.implementedFunctions.BASE, decimalToBaseWithMinimumPadding) } public decimal(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length !== 2) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - const base = this.getNumericArgument(ast, formulaAddress, 1, MIN_BASE, MAX_BASE) - if (base instanceof CellError) { - return base - } - - const input = this.getFirstArgumentAsNumberInBase(ast, formulaAddress, base, DECIMAL_NUMBER_OF_BITS) - if (input instanceof CellError) { - return input - } - - return parseInt(input, base) - } - - private bin2base(ast: ProcedureAst, formulaAddress: SimpleCellAddress, base: number): InternalScalarValue { - if (ast.args.length < 1 || ast.args.length > 2) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - const binaryWithSign = this.getFirstArgumentAsNumberInBase(ast, formulaAddress, 2, NUMBER_OF_BITS) - if (binaryWithSign instanceof CellError) { - return binaryWithSign - } - - let places - if (ast.args.length === 2) { - places = this.getNumericArgument(ast, formulaAddress, 1, 1, 10) - if (places instanceof CellError) { - return places - } - } - - const decimal = twoComplementToDecimal(binaryWithSign) - return decimalToBaseWithExactPadding(decimal, base, places) - } - - private dec2base(ast: ProcedureAst, formulaAddress: SimpleCellAddress, base: number): InternalScalarValue { - if (ast.args.length < 1 || ast.args.length > 2) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - let places - if (ast.args.length === 2) { - places = this.getNumericArgument(ast, formulaAddress, 1, 1, 10) - if (places instanceof CellError) { - return places - } - } - - const min = -Math.pow(base, NUMBER_OF_BITS) / 2 - const max = -min - 1 - - const value = this.getNumericArgument(ast, formulaAddress, 0, min, max) - if (value instanceof CellError) { - return value - } - - return decimalToBaseWithExactPadding(value, base, places) - } - - private getFirstArgumentAsNumberInBase(ast: ProcedureAst, formulaAddress: SimpleCellAddress, base: number, maxLength: number): string | CellError { - const arg = this.evaluateAst(ast.args[0], formulaAddress) - - if (arg instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - const value = coerceScalarToString(arg) - if (typeof value === 'string') { - const baseAlphabet = ALPHABET.substr(0, base) - const regex = new RegExp(`^[${baseAlphabet}]+$`) - if (value.length > maxLength || !regex.test(value)) { + return this.runFunction(ast.args, formulaAddress, RadixConversionPlugin.implementedFunctions.DECIMAL, (arg, base) => { + const input = coerceStringToBase(arg, base, DECIMAL_NUMBER_OF_BITS) + if(input === undefined) { return new CellError(ErrorType.NUM) } - } + return parseInt(input, base) + }) + } +} - return value +function coerceStringToBase(value: string, base: number, maxLength: number): Maybe { + const baseAlphabet = ALPHABET.substr(0, base) + const regex = new RegExp(`^[${baseAlphabet}]+$`) + if (value.length > maxLength || !regex.test(value)) { + return undefined } + return value } function decimalToBaseWithExactPadding(value: number, base: number, places?: number): string | CellError { @@ -214,6 +158,14 @@ function decimalToBaseWithExactPadding(value: number, base: number, places?: num } } +function minValFromBase(base: number) { + return -Math.pow(base, NUMBER_OF_BITS) / 2 +} + +function maxValFromBase(base: number) { + return -minValFromBase(base) - 1 +} + function decimalToBaseWithMinimumPadding(value: number, base: number, places?: number): string { const result = decimalToRadixComplement(value, base) if (places !== undefined && places > result.length) { From e6d92a547ee81c2b035481b30c6053bd26b1be48 Mon Sep 17 00:00:00 2001 From: izulin Date: Mon, 27 Jul 2020 14:40:13 +0200 Subject: [PATCH 54/97] repeated args fixed --- src/interpreter/plugin/BooleanPlugin.ts | 60 +++++++----------------- src/interpreter/plugin/FunctionPlugin.ts | 24 ++++++---- test/interpreter/function-and.spec.ts | 18 +++++-- test/interpreter/function-or.spec.ts | 17 ++++++- test/interpreter/function-xor.spec.ts | 17 ++++++- 5 files changed, 77 insertions(+), 59 deletions(-) diff --git a/src/interpreter/plugin/BooleanPlugin.ts b/src/interpreter/plugin/BooleanPlugin.ts index d33771e1f6..eabcc405d9 100644 --- a/src/interpreter/plugin/BooleanPlugin.ts +++ b/src/interpreter/plugin/BooleanPlugin.ts @@ -33,7 +33,7 @@ export class BooleanPlugin extends FunctionPlugin { 'AND': { method: 'and', parameters: [ - { argumentType: 'boolean', softCoerce: true }, + { argumentType: 'boolean' }, ], repeatedArg: true, expandRanges: true, @@ -41,7 +41,7 @@ export class BooleanPlugin extends FunctionPlugin { 'OR': { method: 'or', parameters: [ - { argumentType: 'boolean', softCoerce: true }, + { argumentType: 'boolean' }, ], repeatedArg: true, expandRanges: true, @@ -49,7 +49,7 @@ export class BooleanPlugin extends FunctionPlugin { 'XOR': { method: 'xor', parameters: [ - { argumentType: 'boolean', softCoerce: true }, + { argumentType: 'boolean' }, ], repeatedArg: true, expandRanges: true, @@ -127,11 +127,7 @@ export class BooleanPlugin extends FunctionPlugin { */ public conditionalIf(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InterpreterValue { return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.IF, (condition, arg2, arg3) => { - if(condition===undefined) { - return new CellError(ErrorType.VALUE) - } else { return condition ? arg2 : arg3 - } }) } @@ -144,15 +140,9 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public and(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.AND, (...args) => { - if(args.some((arg: Maybe) => arg===false)) { - return false - } else if(args.some((arg: Maybe) => arg!==undefined)) { - return true - } else { - return new CellError(ErrorType.VALUE) - } - }) + return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.AND, + (...args) => !args.some( (arg:boolean) => !arg) + ) } /** @@ -164,40 +154,24 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public or(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.OR, (...args) => { - if(args.some((arg: Maybe) => arg===true)) { - return true - } else if(args.some((arg: Maybe) => arg!==undefined)) { - return false - } else { - return new CellError(ErrorType.VALUE) - } - }) + return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.OR, + (...args) => args.some( (arg:boolean) => arg) + ) } public not(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.NOT, (arg) => { - if(arg===undefined) { - return new CellError(ErrorType.VALUE) - } else { - return !arg - } - }) + return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.NOT, (arg) => !arg) } public xor(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.XOR, (...args) => { - if(args.some((arg: Maybe) => arg!==undefined)) { - let cnt = 0 - args.forEach((arg: Maybe) => { - if( arg===true ) { - cnt++ - } - }) - return (cnt%2) === 1 - } else { - return new CellError(ErrorType.VALUE) - } + let cnt = 0 + args.forEach((arg: boolean) => { + if( arg ) { + cnt++ + } + }) + return (cnt%2) === 1 }) } diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 3601e0633e..86237a9392 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -76,15 +76,15 @@ export abstract class FunctionPlugin { return this.interpreter.evaluateAst(ast, formulaAddress) } - protected* iterateOverScalarValues(asts: Ast[], formulaAddress: SimpleCellAddress): IterableIterator { + protected* iterateOverScalarValues(asts: Ast[], formulaAddress: SimpleCellAddress): IterableIterator<[InternalScalarValue,boolean]> { for (const argAst of asts) { const value = this.evaluateAst(argAst, formulaAddress) if (value instanceof SimpleRangeValue) { for (const scalarValue of value.valuesFromTopLeftCorner()) { - yield scalarValue + yield [scalarValue, true] } } else { - yield value + yield [value, false] } } } @@ -155,11 +155,11 @@ export abstract class FunctionPlugin { ) => { const argumentDefinitions: FunctionArgumentDefinition[] = functionDefinition.parameters! assert(argumentDefinitions !== undefined) - let scalarValues: InterpreterValue[] + let scalarValues: [InterpreterValue, boolean][] if(functionDefinition.expandRanges) { scalarValues = [...this.iterateOverScalarValues(args, formulaAddress)] } else { - scalarValues = args.map((ast) => this.evaluateAst(ast, formulaAddress)) + scalarValues = args.map((ast) => [this.evaluateAst(ast, formulaAddress), false]) } const coercedArguments: Maybe[] = [] @@ -174,7 +174,8 @@ export abstract class FunctionPlugin { return new CellError(ErrorType.NA) } } - const arg = scalarValues[i] ?? argumentDefinitions[j]?.defaultValue + const [val,ignorable] = scalarValues[i] ?? [undefined, undefined] + const arg = val ?? argumentDefinitions[j]?.defaultValue if(arg === undefined) { if(argumentDefinitions[j]?.optionalArg) { i++ @@ -185,9 +186,14 @@ export abstract class FunctionPlugin { return new CellError(ErrorType.NA) } } - const coercedArg = scalarValues[i] !== undefined ? this.coerceToType(arg, argumentDefinitions[j]) : arg - if(coercedArg === undefined && !argumentDefinitions[j].softCoerce) { - argCoerceFailure = argCoerceFailure ?? (new CellError(ErrorType.VALUE)) + const coercedArg = val !== undefined ? this.coerceToType(arg, argumentDefinitions[j]) : arg + if(coercedArg === undefined) { + if(!ignorable) { + argCoerceFailure = argCoerceFailure ?? (new CellError(ErrorType.VALUE)) + } + i++ + j++ + continue } if (coercedArg instanceof CellError && argumentDefinitions[j].argumentType !== 'scalar') { argCoerceFailure = argCoerceFailure ?? coercedArg diff --git a/test/interpreter/function-and.spec.ts b/test/interpreter/function-and.spec.ts index 2958f16db3..64444770cb 100644 --- a/test/interpreter/function-and.spec.ts +++ b/test/interpreter/function-and.spec.ts @@ -1,5 +1,4 @@ -import {HyperFormula} from '../../src' -import {ErrorType} from '../../src/Cell' +import {ErrorType, HyperFormula} from '../../src' import {adr, detailedError} from '../testUtils' describe('Function AND', () => { @@ -22,14 +21,27 @@ describe('Function AND', () => { expect(engine.getCellValue(adr('C1'))).toBe(true) }) - it('use coercion', () => { + it('use coercion #1', () => { const engine = HyperFormula.buildFromArray([ ['=AND("TRUE", 1)'], ['=AND("foo", TRUE())'], ]) + expect(engine.getCellValue(adr('A1'))).toBe(true) + expect(engine.getCellValue(adr('A2'))).toEqual(detailedError(ErrorType.VALUE)) + }) + + it('use coercion #2', () => { + const engine = HyperFormula.buildFromArray([ + ['=AND(A4:B4)'], + ['=AND(C4:D4)'], + ['=AND(C4:D4, "foo")'], + ["TRUE", 1, "foo", "=TRUE()"], + ]) + expect(engine.getCellValue(adr('A1'))).toBe(true) expect(engine.getCellValue(adr('A2'))).toBe(true) + expect(engine.getCellValue(adr('A3'))).toEqual(detailedError(ErrorType.VALUE)) }) it('if error in range found, returns first one in row-by-row order', () => { diff --git a/test/interpreter/function-or.spec.ts b/test/interpreter/function-or.spec.ts index cff8d05a15..6b0f6fcce0 100644 --- a/test/interpreter/function-or.spec.ts +++ b/test/interpreter/function-or.spec.ts @@ -14,14 +14,27 @@ describe('Function OR', () => { expect(engine.getCellValue(adr('D1'))).toEqual(detailedError(ErrorType.VALUE)) }) - it('use coercion', () => { + it('use coercion #1', () => { const engine = HyperFormula.buildFromArray([ ['=OR("TRUE", 0)'], ['=OR("foo", 0)'], ]) expect(engine.getCellValue(adr('A1'))).toBe(true) - expect(engine.getCellValue(adr('A2'))).toBe(false) + expect(engine.getCellValue(adr('A2'))).toEqual(detailedError(ErrorType.VALUE)) + }) + + it('use coercion #2', () => { + const engine = HyperFormula.buildFromArray([ + ['=OR(A4:B4)'], + ['=OR(C4:D4)'], + ['=OR(C4:D4, "foo")'], + ["TRUE", 1, "foo", "=TRUE()"], + ]) + + expect(engine.getCellValue(adr('A1'))).toBe(true) + expect(engine.getCellValue(adr('A2'))).toBe(true) + expect(engine.getCellValue(adr('A3'))).toEqual(detailedError(ErrorType.VALUE)) }) it('function OR with numerical arguments', () => { diff --git a/test/interpreter/function-xor.spec.ts b/test/interpreter/function-xor.spec.ts index ff377ff095..561f9b1fdc 100644 --- a/test/interpreter/function-xor.spec.ts +++ b/test/interpreter/function-xor.spec.ts @@ -35,7 +35,7 @@ describe('Function XOR', () => { expect(engine.getCellValue(adr('A2'))).toBe(false) }) - it('use coercion', () => { + it('use coercion #1', () => { const engine = HyperFormula.buildFromArray([ ['=XOR("TRUE")'], ['=XOR(1)'], @@ -44,7 +44,20 @@ describe('Function XOR', () => { expect(engine.getCellValue(adr('A1'))).toBe(true) expect(engine.getCellValue(adr('A2'))).toBe(true) - expect(engine.getCellValue(adr('A3'))).toBe(true) + expect(engine.getCellValue(adr('A3'))).toEqual(detailedError(ErrorType.VALUE)) + }) + + it('use coercion #2', () => { + const engine = HyperFormula.buildFromArray([ + ['=XOR(A4:B4)'], + ['=XOR(C4:D4)'], + ['=XOR(C4:D4, "foo")'], + ["TRUE", 1, "foo", "=TRUE()"], + ]) + + expect(engine.getCellValue(adr('A1'))).toBe(false) + expect(engine.getCellValue(adr('A2'))).toBe(true) + expect(engine.getCellValue(adr('A3'))).toEqual(detailedError(ErrorType.VALUE)) }) it('when no coercible to number arguments', () => { From 314732dd968b4b6ad92760d276c6aa7b77c0544a Mon Sep 17 00:00:00 2001 From: izulin Date: Mon, 27 Jul 2020 14:42:39 +0200 Subject: [PATCH 55/97] linter --- src/interpreter/plugin/BooleanPlugin.ts | 4 ++-- src/interpreter/plugin/FunctionPlugin.ts | 4 ++-- test/interpreter/function-and.spec.ts | 2 +- test/interpreter/function-or.spec.ts | 2 +- test/interpreter/function-xor.spec.ts | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/interpreter/plugin/BooleanPlugin.ts b/src/interpreter/plugin/BooleanPlugin.ts index eabcc405d9..2e57257f14 100644 --- a/src/interpreter/plugin/BooleanPlugin.ts +++ b/src/interpreter/plugin/BooleanPlugin.ts @@ -141,7 +141,7 @@ export class BooleanPlugin extends FunctionPlugin { */ public and(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.AND, - (...args) => !args.some( (arg:boolean) => !arg) + (...args) => !args.some( (arg: boolean) => !arg) ) } @@ -155,7 +155,7 @@ export class BooleanPlugin extends FunctionPlugin { */ public or(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.OR, - (...args) => args.some( (arg:boolean) => arg) + (...args) => args.some( (arg: boolean) => arg) ) } diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 86237a9392..be5d68d692 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -76,7 +76,7 @@ export abstract class FunctionPlugin { return this.interpreter.evaluateAst(ast, formulaAddress) } - protected* iterateOverScalarValues(asts: Ast[], formulaAddress: SimpleCellAddress): IterableIterator<[InternalScalarValue,boolean]> { + protected* iterateOverScalarValues(asts: Ast[], formulaAddress: SimpleCellAddress): IterableIterator<[InternalScalarValue, boolean]> { for (const argAst of asts) { const value = this.evaluateAst(argAst, formulaAddress) if (value instanceof SimpleRangeValue) { @@ -174,7 +174,7 @@ export abstract class FunctionPlugin { return new CellError(ErrorType.NA) } } - const [val,ignorable] = scalarValues[i] ?? [undefined, undefined] + const [val, ignorable] = scalarValues[i] ?? [undefined, undefined] const arg = val ?? argumentDefinitions[j]?.defaultValue if(arg === undefined) { if(argumentDefinitions[j]?.optionalArg) { diff --git a/test/interpreter/function-and.spec.ts b/test/interpreter/function-and.spec.ts index 64444770cb..092fb56750 100644 --- a/test/interpreter/function-and.spec.ts +++ b/test/interpreter/function-and.spec.ts @@ -36,7 +36,7 @@ describe('Function AND', () => { ['=AND(A4:B4)'], ['=AND(C4:D4)'], ['=AND(C4:D4, "foo")'], - ["TRUE", 1, "foo", "=TRUE()"], + ['TRUE', 1, 'foo', '=TRUE()'], ]) expect(engine.getCellValue(adr('A1'))).toBe(true) diff --git a/test/interpreter/function-or.spec.ts b/test/interpreter/function-or.spec.ts index 6b0f6fcce0..cf0ec51b1d 100644 --- a/test/interpreter/function-or.spec.ts +++ b/test/interpreter/function-or.spec.ts @@ -29,7 +29,7 @@ describe('Function OR', () => { ['=OR(A4:B4)'], ['=OR(C4:D4)'], ['=OR(C4:D4, "foo")'], - ["TRUE", 1, "foo", "=TRUE()"], + ['TRUE', 1, 'foo', '=TRUE()'], ]) expect(engine.getCellValue(adr('A1'))).toBe(true) diff --git a/test/interpreter/function-xor.spec.ts b/test/interpreter/function-xor.spec.ts index 561f9b1fdc..4cdfb88c37 100644 --- a/test/interpreter/function-xor.spec.ts +++ b/test/interpreter/function-xor.spec.ts @@ -52,7 +52,7 @@ describe('Function XOR', () => { ['=XOR(A4:B4)'], ['=XOR(C4:D4)'], ['=XOR(C4:D4, "foo")'], - ["TRUE", 1, "foo", "=TRUE()"], + ['TRUE', 1, 'foo', '=TRUE()'], ]) expect(engine.getCellValue(adr('A1'))).toBe(false) From e09d9f4b8d30b4fecffcc38d5a3e51bf674cc090 Mon Sep 17 00:00:00 2001 From: izulin Date: Mon, 27 Jul 2020 14:47:28 +0200 Subject: [PATCH 56/97] linter --- src/interpreter/plugin/BooleanPlugin.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/interpreter/plugin/BooleanPlugin.ts b/src/interpreter/plugin/BooleanPlugin.ts index 2e57257f14..4b545137e2 100644 --- a/src/interpreter/plugin/BooleanPlugin.ts +++ b/src/interpreter/plugin/BooleanPlugin.ts @@ -4,7 +4,6 @@ */ import {CellError, ErrorType, InternalNoErrorCellValue, InternalScalarValue, SimpleCellAddress} from '../../Cell' -import {Maybe} from '../../Maybe' import {ProcedureAst} from '../../parser' import {InterpreterValue} from '../InterpreterValue' import {FunctionPlugin} from './FunctionPlugin' From 6648d72285564437a7fe8c1cec6506d450c13fe1 Mon Sep 17 00:00:00 2001 From: izulin Date: Mon, 27 Jul 2020 15:24:10 +0200 Subject: [PATCH 57/97] . --- src/interpreter/plugin/RandomPlugin.ts | 11 ++--------- src/interpreter/plugin/RoundingPlugin.ts | 5 +---- src/interpreter/plugin/SqrtPlugin.ts | 2 +- src/interpreter/plugin/TextPlugin.ts | 3 +-- 4 files changed, 5 insertions(+), 16 deletions(-) diff --git a/src/interpreter/plugin/RandomPlugin.ts b/src/interpreter/plugin/RandomPlugin.ts index ad87065123..f85a65c562 100644 --- a/src/interpreter/plugin/RandomPlugin.ts +++ b/src/interpreter/plugin/RandomPlugin.ts @@ -11,6 +11,7 @@ export class RandomPlugin extends FunctionPlugin { public static implementedFunctions = { 'RAND': { method: 'rand', + parameters: [], isVolatile: true, }, } @@ -24,15 +25,7 @@ export class RandomPlugin extends FunctionPlugin { * @param ast * @param formulaAddress */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars public rand(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length !== 0) { - return new CellError(ErrorType.NA) - } - - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - return Math.random() + return this.runFunction(ast.args, formulaAddress, RandomPlugin.implementedFunctions.RAND, Math.random) } } diff --git a/src/interpreter/plugin/RoundingPlugin.ts b/src/interpreter/plugin/RoundingPlugin.ts index 18eba40037..b714c2c6f0 100644 --- a/src/interpreter/plugin/RoundingPlugin.ts +++ b/src/interpreter/plugin/RoundingPlugin.ts @@ -4,12 +4,9 @@ */ import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' -import {AstNodeType, ProcedureAst} from '../../parser' -import {SimpleRangeValue} from '../InterpreterValue' +import {ProcedureAst} from '../../parser' import {FunctionPlugin} from './FunctionPlugin' -type RoundingFunction = (numberToRound: number, places: number) => number - export function findNextOddNumber(arg: number): number { const ceiled = Math.ceil(arg) return (ceiled % 2 === 1) ? ceiled : ceiled + 1 diff --git a/src/interpreter/plugin/SqrtPlugin.ts b/src/interpreter/plugin/SqrtPlugin.ts index 93734de57b..7ca2c1ab4b 100644 --- a/src/interpreter/plugin/SqrtPlugin.ts +++ b/src/interpreter/plugin/SqrtPlugin.ts @@ -3,7 +3,7 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' +import {InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' import {FunctionPlugin} from './FunctionPlugin' diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index 7db5dd3c38..fb6588beaf 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -4,8 +4,7 @@ */ import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' -import {AstNodeType, ProcedureAst} from '../../parser' -import {coerceScalarToString} from '../ArithmeticHelper' +import {ProcedureAst} from '../../parser' import {FunctionPlugin} from './FunctionPlugin' /** From 4643f2c48f33ff3dfd32bd083b7d61e6eb7f87bb Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Mon, 27 Jul 2020 16:21:37 +0200 Subject: [PATCH 58/97] this.parameter helper method --- src/index.ts | 4 +- src/interpreter/index.ts | 4 +- src/interpreter/plugin/AbsPlugin.ts | 6 +- src/interpreter/plugin/BitShiftPlugin.ts | 12 +- .../plugin/BitwiseLogicOperationsPlugin.ts | 18 +-- src/interpreter/plugin/BooleanPlugin.ts | 150 ++++++++++-------- src/interpreter/plugin/CharPlugin.ts | 10 +- src/interpreter/plugin/CodePlugin.ts | 10 +- src/interpreter/plugin/CountUniquePlugin.ts | 16 +- src/interpreter/plugin/DegreesPlugin.ts | 10 +- src/interpreter/plugin/DeltaPlugin.ts | 12 +- src/interpreter/plugin/ErrorFunctionPlugin.ts | 24 +-- src/interpreter/plugin/ExpPlugin.ts | 6 +- src/interpreter/plugin/FinancialPlugin.ts | 84 +++++----- src/interpreter/plugin/FunctionPlugin.ts | 36 +++-- src/interpreter/plugin/InformationPlugin.ts | 36 ++--- src/interpreter/plugin/IsEvenPlugin.ts | 6 +- src/interpreter/plugin/IsOddPlugin.ts | 10 +- src/interpreter/plugin/LogarithmPlugin.ts | 32 ++-- src/interpreter/plugin/MathConstantsPlugin.ts | 8 +- src/interpreter/plugin/MedianPlugin.ts | 14 +- src/interpreter/plugin/ModuloPlugin.ts | 6 +- src/interpreter/plugin/PowerPlugin.ts | 6 +- src/interpreter/plugin/RadiansPlugin.ts | 6 +- .../plugin/RadixConversionPlugin.ts | 48 +++--- src/interpreter/plugin/RandomPlugin.ts | 8 +- src/interpreter/plugin/RoundingPlugin.ts | 46 +++--- src/interpreter/plugin/SqrtPlugin.ts | 6 +- src/interpreter/plugin/TextPlugin.ts | 130 ++++++++------- src/interpreter/plugin/TrigonometryPlugin.ts | 48 +++--- 30 files changed, 444 insertions(+), 368 deletions(-) diff --git a/src/index.ts b/src/index.ts index abf206947b..0ad80e9f41 100644 --- a/src/index.ts +++ b/src/index.ts @@ -50,7 +50,7 @@ import { UnableToParseError } from './errors' import * as plugins from './interpreter/plugin' -import {FunctionArgumentDefinition, FunctionPlugin, FunctionPluginDefinition} from './interpreter' +import {FunctionArgument, FunctionPlugin, FunctionPluginDefinition} from './interpreter' import {ColumnRowIndex} from './CrudOperations' /** @internal */ @@ -121,7 +121,7 @@ export { ColumnRowIndex, RawTranslationPackage, FunctionPluginDefinition, - FunctionArgumentDefinition, + FunctionArgument, NamedExpression, NamedExpressionOptions, HyperFormula, diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts index bfd9c517a9..3d66ea98d2 100644 --- a/src/interpreter/index.ts +++ b/src/interpreter/index.ts @@ -4,7 +4,7 @@ */ import { - FunctionArgumentDefinition, + FunctionArgument, FunctionMetadata, FunctionPlugin, FunctionPluginDefinition, @@ -14,7 +14,7 @@ import {FunctionTranslationsPackage} from './FunctionRegistry' export { FunctionPluginDefinition, - FunctionArgumentDefinition, + FunctionArgument, FunctionPlugin, ImplementedFunctions, FunctionMetadata, diff --git a/src/interpreter/plugin/AbsPlugin.ts b/src/interpreter/plugin/AbsPlugin.ts index 34de534580..078353f23c 100644 --- a/src/interpreter/plugin/AbsPlugin.ts +++ b/src/interpreter/plugin/AbsPlugin.ts @@ -11,13 +11,13 @@ export class AbsPlugin extends FunctionPlugin { public static implementedFunctions = { 'ABS': { method: 'abs', - parameters: [ + parameters: { list: [ { argumentType: 'number' } - ], + ]} }, } public abs(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, AbsPlugin.implementedFunctions.ABS, Math.abs) + return this.runFunction(ast.args, formulaAddress, this.parameters('ABS'), Math.abs) } } diff --git a/src/interpreter/plugin/BitShiftPlugin.ts b/src/interpreter/plugin/BitShiftPlugin.ts index 445dd87e7e..135bd8d23f 100644 --- a/src/interpreter/plugin/BitShiftPlugin.ts +++ b/src/interpreter/plugin/BitShiftPlugin.ts @@ -15,26 +15,26 @@ export class BitShiftPlugin extends FunctionPlugin { public static implementedFunctions = { 'BITLSHIFT': { method: 'bitlshift', - parameters: [ + parameters: { list: [ { argumentType: 'integer', minValue: 0 }, { argumentType: 'integer', minValue: SHIFT_MIN_POSITIONS, maxValue: SHIFT_MAX_POSITIONS }, - ] + ]} }, 'BITRSHIFT': { method: 'bitrshift', - parameters: [ + parameters: { list: [ { argumentType: 'integer', minValue: 0 }, { argumentType: 'integer', minValue: SHIFT_MIN_POSITIONS, maxValue: SHIFT_MAX_POSITIONS }, - ] + ]} }, } public bitlshift(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, BitShiftPlugin.implementedFunctions.BITLSHIFT, shiftLeft) + return this.runFunction(ast.args, formulaAddress, this.parameters('BITLSHIFT'), shiftLeft) } public bitrshift(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, BitShiftPlugin.implementedFunctions.BITRSHIFT, shiftRight) + return this.runFunction(ast.args, formulaAddress, this.parameters('BITRSHIFT'), shiftRight) } } diff --git a/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts b/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts index 0be06e94c1..f3c04fbfe3 100644 --- a/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts +++ b/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts @@ -11,41 +11,41 @@ export class BitwiseLogicOperationsPlugin extends FunctionPlugin { public static implementedFunctions = { 'BITAND': { method: 'bitand', - parameters: [ + parameters: { list: [ { argumentType: 'integer', minValue: 0 }, { argumentType: 'integer', minValue: 0 }, - ] + ]} }, 'BITOR': { method: 'bitor', - parameters: [ + parameters: { list: [ { argumentType: 'integer', minValue: 0 }, { argumentType: 'integer', minValue: 0 }, - ] + ]} }, 'BITXOR': { method: 'bitxor', - parameters: [ + parameters: { list: [ { argumentType: 'integer', minValue: 0 }, { argumentType: 'integer', minValue: 0 }, - ] + ]} }, } public bitand(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, BitwiseLogicOperationsPlugin.implementedFunctions.BITAND, + return this.runFunction(ast.args, formulaAddress, this.parameters('BITAND'), (left: number, right: number) => left & right ) } public bitor(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, BitwiseLogicOperationsPlugin.implementedFunctions.BITOR, + return this.runFunction(ast.args, formulaAddress, this.parameters('BITOR'), (left: number, right: number) => left | right ) } public bitxor(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, BitwiseLogicOperationsPlugin.implementedFunctions.BITXOR, + return this.runFunction(ast.args, formulaAddress, this.parameters('BITXOR'), (left: number, right: number) => left ^ right ) } diff --git a/src/interpreter/plugin/BooleanPlugin.ts b/src/interpreter/plugin/BooleanPlugin.ts index 4b545137e2..6ed398342c 100644 --- a/src/interpreter/plugin/BooleanPlugin.ts +++ b/src/interpreter/plugin/BooleanPlugin.ts @@ -15,80 +15,98 @@ export class BooleanPlugin extends FunctionPlugin { public static implementedFunctions = { 'TRUE': { method: 'literalTrue', - parameters: [], + parameters: {list: []}, }, 'FALSE': { method: 'literalFalse', - parameters: [], + parameters: {list: []}, }, 'IF': { method: 'conditionalIf', - parameters: [ - { argumentType: 'boolean' }, - { argumentType: 'scalar' }, - { argumentType: 'scalar', defaultValue: false }, - ], + parameters: { + list: [ + {argumentType: 'boolean'}, + {argumentType: 'scalar'}, + {argumentType: 'scalar', defaultValue: false}, + ] + }, }, 'AND': { method: 'and', - parameters: [ - { argumentType: 'boolean' }, - ], - repeatedArg: true, - expandRanges: true, + parameters: { + list: [ + {argumentType: 'boolean'}, + ], + repeatedArg: true, + expandRanges: true, + }, }, 'OR': { method: 'or', - parameters: [ - { argumentType: 'boolean' }, - ], - repeatedArg: true, - expandRanges: true, + parameters: { + list: [ + {argumentType: 'boolean'}, + ], + repeatedArg: true, + expandRanges: true, + }, }, 'XOR': { method: 'xor', - parameters: [ - { argumentType: 'boolean' }, - ], - repeatedArg: true, - expandRanges: true, + parameters: { + list: [ + {argumentType: 'boolean'}, + ], + repeatedArg: true, + expandRanges: true, + }, }, 'NOT': { method: 'not', - parameters: [ - { argumentType: 'boolean' }, - ], + parameters: { + list: [ + {argumentType: 'boolean'}, + ] + }, }, 'SWITCH': { method: 'switch', - parameters: [ - { argumentType: 'noerror' }, - { argumentType: 'scalar' }, - { argumentType: 'scalar' }, - ], - repeatedArg: true, + parameters: { + list: [ + {argumentType: 'noerror'}, + {argumentType: 'scalar'}, + {argumentType: 'scalar'}, + ], + repeatedArg: true, + }, }, 'IFERROR': { method: 'iferror', - parameters: [ - { argumentType: 'scalar' }, - { argumentType: 'scalar' }, - ], + parameters: { + list: [ + {argumentType: 'scalar'}, + {argumentType: 'scalar'}, + ] + }, }, 'IFNA': { method: 'ifna', - parameters: [ - { argumentType: 'scalar' }, - { argumentType: 'scalar' }, - ], + parameters: { + list: [ + {argumentType: 'scalar'}, + {argumentType: 'scalar'}, + ] + }, }, 'CHOOSE': { method: 'choose', - parameters: [ - { argumentType: 'number' }, - { argumentType: 'scalar' }, - ], - repeatedArg: true, + parameters: { + list: [ + {argumentType: 'number'}, + {argumentType: 'scalar'}, + ], + repeatedArg: true, + }, }, } @@ -101,7 +119,7 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public literalTrue(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.TRUE, () => true) + return this.runFunction(ast.args, formulaAddress, this.parameters('TRUE'), () => true) } /** @@ -113,7 +131,7 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public literalFalse(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.FALSE, () => false) + return this.runFunction(ast.args, formulaAddress, this.parameters('FALSE'), () => false) } /** @@ -125,8 +143,8 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public conditionalIf(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InterpreterValue { - return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.IF, (condition, arg2, arg3) => { - return condition ? arg2 : arg3 + return this.runFunction(ast.args, formulaAddress, this.parameters('IF'), (condition, arg2, arg3) => { + return condition ? arg2 : arg3 }) } @@ -139,8 +157,8 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public and(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.AND, - (...args) => !args.some( (arg: boolean) => !arg) + return this.runFunction(ast.args, formulaAddress, this.parameters('AND'), + (...args) => !args.some((arg: boolean) => !arg) ) } @@ -153,37 +171,37 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public or(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.OR, - (...args) => args.some( (arg: boolean) => arg) + return this.runFunction(ast.args, formulaAddress, this.parameters('OR'), + (...args) => args.some((arg: boolean) => arg) ) } public not(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.NOT, (arg) => !arg) + return this.runFunction(ast.args, formulaAddress, this.parameters('NOT'), (arg) => !arg) } public xor(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.XOR, (...args) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('XOR'), (...args) => { let cnt = 0 args.forEach((arg: boolean) => { - if( arg ) { + if (arg) { cnt++ } }) - return (cnt%2) === 1 + return (cnt % 2) === 1 }) } public switch(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.SWITCH, (selector, ...args) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('SWITCH'), (selector, ...args) => { const n = args.length - let i = 0 + let i = 0 for (; i + 1 < n; i += 2) { if (args[i] instanceof CellError) { continue } if (this.interpreter.arithmeticHelper.compare(selector, args[i] as InternalNoErrorCellValue) === 0) { - return args[i+1] + return args[i + 1] } } if (i < n) { @@ -195,8 +213,8 @@ export class BooleanPlugin extends FunctionPlugin { } public iferror(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.IFERROR, (arg1: InternalScalarValue, arg2: InternalScalarValue) => { - if(arg1 instanceof CellError) { + return this.runFunction(ast.args, formulaAddress, this.parameters('IFERROR'), (arg1: InternalScalarValue, arg2: InternalScalarValue) => { + if (arg1 instanceof CellError) { return arg2 } else { return arg1 @@ -205,8 +223,8 @@ export class BooleanPlugin extends FunctionPlugin { } public ifna(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.IFNA, (arg1: InternalScalarValue, arg2: InternalScalarValue) => { - if(arg1 instanceof CellError && arg1.type === ErrorType.NA) { + return this.runFunction(ast.args, formulaAddress, this.parameters('IFNA'), (arg1: InternalScalarValue, arg2: InternalScalarValue) => { + if (arg1 instanceof CellError && arg1.type === ErrorType.NA) { return arg2 } else { return arg1 @@ -215,11 +233,11 @@ export class BooleanPlugin extends FunctionPlugin { } public choose(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, BooleanPlugin.implementedFunctions.CHOOSE, (selector, ...args) => { - if(selector !== Math.round(selector) || selector<1 || selector > args.length) { + return this.runFunction(ast.args, formulaAddress, this.parameters('CHOOSE'), (selector, ...args) => { + if (selector !== Math.round(selector) || selector < 1 || selector > args.length) { return new CellError(ErrorType.NUM) } - return args[selector-1] + return args[selector - 1] }) } } diff --git a/src/interpreter/plugin/CharPlugin.ts b/src/interpreter/plugin/CharPlugin.ts index 47b60222bf..3e4f318307 100644 --- a/src/interpreter/plugin/CharPlugin.ts +++ b/src/interpreter/plugin/CharPlugin.ts @@ -11,14 +11,16 @@ export class CharPlugin extends FunctionPlugin { public static implementedFunctions = { 'CHAR': { method: 'char', - parameters: [ - { argumentType: 'number' } - ], + parameters: { + list: [ + {argumentType: 'number'} + ], + } }, } public char(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, CharPlugin.implementedFunctions.CHAR, (value: number) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('CHAR'), (value: number) => { if (value < 1 || value > 255) { return new CellError(ErrorType.NUM) } diff --git a/src/interpreter/plugin/CodePlugin.ts b/src/interpreter/plugin/CodePlugin.ts index 27974d6797..62ecfa1023 100644 --- a/src/interpreter/plugin/CodePlugin.ts +++ b/src/interpreter/plugin/CodePlugin.ts @@ -11,14 +11,16 @@ export class CodePlugin extends FunctionPlugin { public static implementedFunctions = { 'CODE': { method: 'code', - parameters: [ - { argumentType: 'string' } - ], + parameters: { + list: [ + {argumentType: 'string'} + ] + }, }, } public code(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, CodePlugin.implementedFunctions.CODE, (value: string) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('CODE'), (value: string) => { if (value.length === 0) { return new CellError(ErrorType.VALUE) } diff --git a/src/interpreter/plugin/CountUniquePlugin.ts b/src/interpreter/plugin/CountUniquePlugin.ts index 32569ad9bb..e20965f3eb 100644 --- a/src/interpreter/plugin/CountUniquePlugin.ts +++ b/src/interpreter/plugin/CountUniquePlugin.ts @@ -14,11 +14,13 @@ export class CountUniquePlugin extends FunctionPlugin { public static implementedFunctions = { 'COUNTUNIQUE': { method: 'countunique', - parameters: [ - { argumentType: 'scalar' }, - ], - repeatedArg: true, - expandRanges: true, + parameters: { + list: [ + {argumentType: 'scalar'}, + ], + repeatedArg: true, + expandRanges: true, + }, }, } @@ -31,11 +33,11 @@ export class CountUniquePlugin extends FunctionPlugin { * @param formulaAddress */ public countunique(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, CountUniquePlugin.implementedFunctions.COUNTUNIQUE, (...args: InternalScalarValue[]) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('COUNTUNIQUE'), (...args: InternalScalarValue[]) => { const valuesSet = new Set() const errorsSet = new Set() - for (const scalarValue of args) { + for (const scalarValue of args) { if (scalarValue instanceof CellError) { errorsSet.add(scalarValue.type) } else if (scalarValue !== '') { diff --git a/src/interpreter/plugin/DegreesPlugin.ts b/src/interpreter/plugin/DegreesPlugin.ts index 8a08df9d19..05c82c4de5 100644 --- a/src/interpreter/plugin/DegreesPlugin.ts +++ b/src/interpreter/plugin/DegreesPlugin.ts @@ -11,14 +11,16 @@ export class DegreesPlugin extends FunctionPlugin { public static implementedFunctions = { 'DEGREES': { method: 'degrees', - parameters: [ - { argumentType: 'number' } - ], + parameters: { + list: [ + {argumentType: 'number'} + ] + }, }, } public degrees(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, DegreesPlugin.implementedFunctions.DEGREES, + return this.runFunction(ast.args, formulaAddress, this.parameters('DEGREES'), (arg) => arg * (180 / Math.PI) ) } diff --git a/src/interpreter/plugin/DeltaPlugin.ts b/src/interpreter/plugin/DeltaPlugin.ts index 631f280c4f..5ecba964a5 100644 --- a/src/interpreter/plugin/DeltaPlugin.ts +++ b/src/interpreter/plugin/DeltaPlugin.ts @@ -11,15 +11,17 @@ export class DeltaPlugin extends FunctionPlugin { public static implementedFunctions = { 'DELTA': { method: 'delta', - parameters: [ - { argumentType: 'number' }, - { argumentType: 'number', defaultValue: 0 }, - ], + parameters: { + list: [ + {argumentType: 'number'}, + {argumentType: 'number', defaultValue: 0}, + ] + }, }, } public delta(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, DeltaPlugin.implementedFunctions.DELTA, + return this.runFunction(ast.args, formulaAddress, this.parameters('DELTA'), (left: number, right: number) => (left === right ? 1 : 0) ) } diff --git a/src/interpreter/plugin/ErrorFunctionPlugin.ts b/src/interpreter/plugin/ErrorFunctionPlugin.ts index 1afbdb682c..d5930243ad 100644 --- a/src/interpreter/plugin/ErrorFunctionPlugin.ts +++ b/src/interpreter/plugin/ErrorFunctionPlugin.ts @@ -11,22 +11,26 @@ export class ErrorFunctionPlugin extends FunctionPlugin { public static implementedFunctions = { 'ERF': { method: 'erf', - parameters: [ - { argumentType: 'number' }, - { argumentType: 'number', optionalArg: true}, - ], + parameters: { + list: [ + {argumentType: 'number'}, + {argumentType: 'number', optionalArg: true}, + ] + }, }, 'ERFC': { method: 'erfc', - parameters: [ - { argumentType: 'number' } - ], + parameters: { + list: [ + {argumentType: 'number'} + ] + }, }, } public erf(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, ErrorFunctionPlugin.implementedFunctions.ERF, (lowerBound, upperBound) => { - if(upperBound===undefined) { + return this.runFunction(ast.args, formulaAddress, this.parameters('ERF'), (lowerBound, upperBound) => { + if (upperBound === undefined) { return erf(lowerBound) } else { return erf(upperBound) - erf(lowerBound) @@ -35,7 +39,7 @@ export class ErrorFunctionPlugin extends FunctionPlugin { } public erfc(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, ErrorFunctionPlugin.implementedFunctions.ERFC, erfc) + return this.runFunction(ast.args, formulaAddress, this.parameters('ERFC'), erfc) } } diff --git a/src/interpreter/plugin/ExpPlugin.ts b/src/interpreter/plugin/ExpPlugin.ts index af1286cedd..7350cbfbc5 100644 --- a/src/interpreter/plugin/ExpPlugin.ts +++ b/src/interpreter/plugin/ExpPlugin.ts @@ -11,9 +11,9 @@ export class ExpPlugin extends FunctionPlugin { public static implementedFunctions = { 'EXP': { method: 'exp', - parameters: [ + parameters: { list: [ { argumentType: 'number' } - ], + ]}, }, } @@ -26,6 +26,6 @@ export class ExpPlugin extends FunctionPlugin { * @param formulaAddress */ public exp(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, ExpPlugin.implementedFunctions.EXP, Math.exp) + return this.runFunction(ast.args, formulaAddress, this.parameters('EXP'), Math.exp) } } diff --git a/src/interpreter/plugin/FinancialPlugin.ts b/src/interpreter/plugin/FinancialPlugin.ts index 1c904b3ef7..5f98e39f0f 100644 --- a/src/interpreter/plugin/FinancialPlugin.ts +++ b/src/interpreter/plugin/FinancialPlugin.ts @@ -11,71 +11,79 @@ export class FinancialPlugin extends FunctionPlugin { public static implementedFunctions = { 'PMT': { method: 'pmt', - parameters: [ - { argumentType: 'number' }, - { argumentType: 'number' }, - { argumentType: 'number' }, - { argumentType: 'number', defaultValue: 0 }, - { argumentType: 'number', defaultValue: 0 }, - ], + parameters: { + list: [ + {argumentType: 'number'}, + {argumentType: 'number'}, + {argumentType: 'number'}, + {argumentType: 'number', defaultValue: 0}, + {argumentType: 'number', defaultValue: 0}, + ] + }, }, 'IPMT': { method: 'ipmt', - parameters: [ - { argumentType: 'number' }, - { argumentType: 'number' }, - { argumentType: 'number' }, - { argumentType: 'number' }, - { argumentType: 'number', defaultValue: 0 }, - { argumentType: 'number', defaultValue: 0 }, - ], + parameters: { + list: [ + {argumentType: 'number'}, + {argumentType: 'number'}, + {argumentType: 'number'}, + {argumentType: 'number'}, + {argumentType: 'number', defaultValue: 0}, + {argumentType: 'number', defaultValue: 0}, + ] + }, }, 'PPMT': { method: 'ppmt', - parameters: [ - { argumentType: 'number' }, - { argumentType: 'number' }, - { argumentType: 'number' }, - { argumentType: 'number' }, - { argumentType: 'number', defaultValue: 0 }, - { argumentType: 'number', defaultValue: 0 }, - ], + parameters: { + list: [ + {argumentType: 'number'}, + {argumentType: 'number'}, + {argumentType: 'number'}, + {argumentType: 'number'}, + {argumentType: 'number', defaultValue: 0}, + {argumentType: 'number', defaultValue: 0}, + ] + }, }, 'FV': { method: 'fv', - parameters: [ - { argumentType: 'number' }, - { argumentType: 'number' }, - { argumentType: 'number' }, - { argumentType: 'number', defaultValue: 0 }, - { argumentType: 'number', defaultValue: 0 }, - ], + parameters: { + list: [ + {argumentType: 'number'}, + {argumentType: 'number'}, + {argumentType: 'number'}, + {argumentType: 'number', defaultValue: 0}, + {argumentType: 'number', defaultValue: 0}, + ] + }, }, } public pmt(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, FinancialPlugin.implementedFunctions.PMT, pmtCore) + return this.runFunction(ast.args, formulaAddress, this.parameters('PMT'), pmtCore) } public ipmt(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, FinancialPlugin.implementedFunctions.IPMT, ipmtCore) + return this.runFunction(ast.args, formulaAddress, this.parameters('IPMT'), ipmtCore) } public ppmt(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, FinancialPlugin.implementedFunctions.PPMT, ppmtCore) + return this.runFunction(ast.args, formulaAddress, this.parameters('PPMT'), ppmtCore) } public fv(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, FinancialPlugin.implementedFunctions.FV, fvCore) + return this.runFunction(ast.args, formulaAddress, this.parameters('FV'), fvCore) } } function pmtCore(rate: number, periods: number, present: number, future: number, type: number): number { if (rate === 0) { - return (- present - future) / periods + return (-present - future) / periods } else { const term = Math.pow(1 + rate, periods) - return (future * rate + present * rate * term) * (type !== 0 ? 1 / (1 + rate) : 1) / (1-term) + return (future * rate + present * rate * term) * (type !== 0 ? 1 / (1 + rate) : 1) / (1 - term) } } @@ -90,10 +98,10 @@ function ipmtCore(rate: number, period: number, periods: number, present: number function fvCore(rate: number, periods: number, payment: number, value: number, type: number): number { if (rate === 0) { - return - value - payment * periods + return -value - payment * periods } else { const term = Math.pow(1 + rate, periods) - return payment * (type !== 0 ? (1 + rate) : 1) * (1 - term) / rate - value * term + return payment * (type !== 0 ? (1 + rate) : 1) * (1 - term) / rate - value * term } } diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index be5d68d692..d736288f59 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -3,7 +3,6 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import assert from 'assert' import {AbsoluteCellRange} from '../../AbsoluteCellRange' import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ColumnSearchStrategy} from '../../ColumnSearch/ColumnSearchStrategy' @@ -21,9 +20,7 @@ export interface ImplementedFunctions { export interface FunctionMetadata { method: string, - parameters?: FunctionArgumentDefinition[], - repeatedArg?: boolean, - expandRanges?: boolean, + parameters?: FunctionArgumentsDefinition, isVolatile?: boolean, isDependentOnSheetStructureChange?: boolean, doesNotNeedArgumentsToBeComputed?: boolean, @@ -37,11 +34,16 @@ export interface FunctionPluginDefinition { export type ArgumentTypes = 'string' | 'number' | 'boolean' | 'scalar' | 'noerror' | 'range' | 'integer' -export interface FunctionArgumentDefinition { +export interface FunctionArgumentsDefinition { + list: FunctionArgument[], + repeatedArg?: boolean, + expandRanges?: boolean, +} + +export interface FunctionArgument { argumentType: string, defaultValue?: InternalScalarValue, - softCoerce?: boolean, //failed coerce makes function ignore the arg instead of producing error - optionalArg?: boolean, // + optionalArg?: boolean, minValue?: number, maxValue?: number, lessThan?: number, @@ -101,7 +103,7 @@ export abstract class FunctionPlugin { public coerceScalarToNumberOrError = (arg: InternalScalarValue): number | CellError => this.interpreter.arithmeticHelper.coerceScalarToNumberOrError(arg) - public coerceToType(arg: InterpreterValue, coercedType: FunctionArgumentDefinition): Maybe { + public coerceToType(arg: InterpreterValue, coercedType: FunctionArgument): Maybe { if(arg instanceof SimpleRangeValue) { if(coercedType.argumentType === 'range') { return arg @@ -150,17 +152,18 @@ export abstract class FunctionPlugin { protected runFunction = ( args: Ast[], formulaAddress: SimpleCellAddress, - functionDefinition: FunctionMetadata, + functionDefinition: FunctionArgumentsDefinition, fn: (...arg: any) => InternalScalarValue ) => { - const argumentDefinitions: FunctionArgumentDefinition[] = functionDefinition.parameters! - assert(argumentDefinitions !== undefined) + const argumentDefinitions: FunctionArgument[] = functionDefinition.list let scalarValues: [InterpreterValue, boolean][] + if(functionDefinition.expandRanges) { scalarValues = [...this.iterateOverScalarValues(args, formulaAddress)] } else { scalarValues = args.map((ast) => [this.evaluateAst(ast, formulaAddress), false]) } + const coercedArguments: Maybe[] = [] let argCoerceFailure: Maybe = undefined @@ -206,10 +209,11 @@ export abstract class FunctionPlugin { return argCoerceFailure ?? fn(...coercedArguments) } - protected evaluateArgOrDefault = (formulaAddress: SimpleCellAddress, argAst?: Ast, defaultValue?: InternalScalarValue): InterpreterValue => { - if (argAst !== undefined) { - return this.evaluateAst(argAst, formulaAddress) + protected parameters(name: string): FunctionArgumentsDefinition { + const params = (this.constructor as FunctionPluginDefinition).implementedFunctions[name]?.parameters + if (params !== undefined) { + return params } - return defaultValue ?? new CellError(ErrorType.NA) + throw new Error('FIXME Should not be undefined') } -} +} \ No newline at end of file diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index 99b9b2828a..390e0694fd 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -15,39 +15,39 @@ export class InformationPlugin extends FunctionPlugin { public static implementedFunctions = { 'ISERROR': { method: 'iserror', - parameters: [ + parameters: { list: [ { argumentType: 'scalar'} - ] + ]} }, 'ISBLANK': { method: 'isblank', - parameters: [ + parameters: { list: [ { argumentType: 'scalar'} - ] + ]} }, 'ISNUMBER': { method: 'isnumber', - parameters: [ + parameters: { list: [ { argumentType: 'scalar'} - ] + ]} }, 'ISLOGICAL': { method: 'islogical', - parameters: [ + parameters: { list: [ { argumentType: 'scalar'} - ] + ]} }, 'ISTEXT': { method: 'istext', - parameters: [ + parameters: { list: [ { argumentType: 'scalar'} - ] + ]} }, 'ISNONTEXT': { method: 'isnontext', - parameters: [ + parameters: { list: [ { argumentType: 'scalar'} - ] + ]} }, 'COLUMNS': { method: 'columns', @@ -73,7 +73,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public iserror(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISERROR, (arg: InternalScalarValue) => + return this.runFunction(ast.args, formulaAddress, this.parameters('ISERROR'), (arg: InternalScalarValue) => (arg instanceof CellError) ) } @@ -87,7 +87,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public isblank(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISBLANK, (arg: InternalScalarValue) => + return this.runFunction(ast.args, formulaAddress, this.parameters('ISBLANK'), (arg: InternalScalarValue) => (arg === EmptyValue) ) } @@ -101,7 +101,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public isnumber(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISNUMBER, (arg: InternalScalarValue) => + return this.runFunction(ast.args, formulaAddress, this.parameters('ISNUMBER'), (arg: InternalScalarValue) => (typeof arg === 'number') ) } @@ -115,7 +115,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public islogical(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISLOGICAL, (arg: InternalScalarValue) => + return this.runFunction(ast.args, formulaAddress, this.parameters('ISLOGICAL'), (arg: InternalScalarValue) => (typeof arg === 'boolean') ) } @@ -129,7 +129,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public istext(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISTEXT, (arg: InternalScalarValue) => + return this.runFunction(ast.args, formulaAddress, this.parameters('ISTEXT'), (arg: InternalScalarValue) => (typeof arg === 'string') ) } @@ -143,7 +143,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public isnontext(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISNONTEXT, (arg: InternalScalarValue) => + return this.runFunction(ast.args, formulaAddress, this.parameters('ISNONTEXT'), (arg: InternalScalarValue) => (typeof arg !== 'string') ) } diff --git a/src/interpreter/plugin/IsEvenPlugin.ts b/src/interpreter/plugin/IsEvenPlugin.ts index 37bfd85cc5..f8154d7b4e 100644 --- a/src/interpreter/plugin/IsEvenPlugin.ts +++ b/src/interpreter/plugin/IsEvenPlugin.ts @@ -11,14 +11,14 @@ export class IsEvenPlugin extends FunctionPlugin { public static implementedFunctions = { 'ISEVEN': { method: 'iseven', - parameters: [ + parameters: { list: [ { argumentType: 'number'} - ] + ]} }, } public iseven(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, IsEvenPlugin.implementedFunctions.ISEVEN, + return this.runFunction(ast.args, formulaAddress, this.parameters('ISEVEN'), (val) => (val % 2 === 0) ) } diff --git a/src/interpreter/plugin/IsOddPlugin.ts b/src/interpreter/plugin/IsOddPlugin.ts index b83e621d02..9398305409 100644 --- a/src/interpreter/plugin/IsOddPlugin.ts +++ b/src/interpreter/plugin/IsOddPlugin.ts @@ -11,14 +11,16 @@ export class IsOddPlugin extends FunctionPlugin { public static implementedFunctions = { 'ISODD': { method: 'isodd', - parameters: [ - { argumentType: 'number'} - ] + parameters: { + list: [ + {argumentType: 'number'} + ] + } }, } public isodd(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, IsOddPlugin.implementedFunctions.ISODD, + return this.runFunction(ast.args, formulaAddress, this.parameters('ISODD'), (val) => (val % 2 === 1) ) } diff --git a/src/interpreter/plugin/LogarithmPlugin.ts b/src/interpreter/plugin/LogarithmPlugin.ts index 0884a1128c..dafbbacd87 100644 --- a/src/interpreter/plugin/LogarithmPlugin.ts +++ b/src/interpreter/plugin/LogarithmPlugin.ts @@ -12,36 +12,42 @@ export class LogarithmPlugin extends FunctionPlugin { public static implementedFunctions = { 'LOG10': { method: 'log10', - parameters: [ - { argumentType: 'number' } - ], + parameters: { + list: [ + {argumentType: 'number'} + ] + }, }, 'LOG': { method: 'log', - parameters: [ - { argumentType: 'number', greaterThan: 0 }, - { argumentType: 'number', defaultValue: 10, greaterThan: 0 }, - ], + parameters: { + list: [ + {argumentType: 'number', greaterThan: 0}, + {argumentType: 'number', defaultValue: 10, greaterThan: 0}, + ] + }, }, 'LN': { method: 'ln', - parameters: [ - { argumentType: 'number' } - ], + parameters: { + list: [ + {argumentType: 'number'} + ] + }, }, } public log10(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, LogarithmPlugin.implementedFunctions.LOG10, Math.log10) + return this.runFunction(ast.args, formulaAddress, this.parameters('LOG10'), Math.log10) } public log(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, LogarithmPlugin.implementedFunctions.LOG, + return this.runFunction(ast.args, formulaAddress, this.parameters('LOG'), (arg: number, base: number) => Math.log(arg) / Math.log(base) ) } public ln(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, LogarithmPlugin.implementedFunctions.LN, Math.log) + return this.runFunction(ast.args, formulaAddress, this.parameters('LN'), Math.log) } } diff --git a/src/interpreter/plugin/MathConstantsPlugin.ts b/src/interpreter/plugin/MathConstantsPlugin.ts index bafc1a460b..174e2f8571 100644 --- a/src/interpreter/plugin/MathConstantsPlugin.ts +++ b/src/interpreter/plugin/MathConstantsPlugin.ts @@ -14,22 +14,22 @@ export class MathConstantsPlugin extends FunctionPlugin { public static implementedFunctions = { 'PI': { method: 'pi', - parameters: [], + parameters: {list: []}, }, 'E': { method: 'e', - parameters: [], + parameters: {list: []}, }, } public pi(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, MathConstantsPlugin.implementedFunctions.PI, + return this.runFunction(ast.args, formulaAddress, this.parameters('PI'), () => PI ) } public e(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, MathConstantsPlugin.implementedFunctions.E, + return this.runFunction(ast.args, formulaAddress, this.parameters('E'), () => E ) } diff --git a/src/interpreter/plugin/MedianPlugin.ts b/src/interpreter/plugin/MedianPlugin.ts index 11f7cba99d..4163ba09aa 100644 --- a/src/interpreter/plugin/MedianPlugin.ts +++ b/src/interpreter/plugin/MedianPlugin.ts @@ -15,11 +15,13 @@ export class MedianPlugin extends FunctionPlugin { public static implementedFunctions = { 'MEDIAN': { method: 'median', - parameters: [ - { argumentType: 'noerror' }, - ], - repeatedArg: true, - expandRanges: true, + parameters: { + list: [ + {argumentType: 'noerror'}, + ], + repeatedArg: true, + expandRanges: true, + }, }, } @@ -32,7 +34,7 @@ export class MedianPlugin extends FunctionPlugin { * @param formulaAddress */ public median(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, MedianPlugin.implementedFunctions.MEDIAN, (...args) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('MEDIAN'), (...args) => { const values: number[] = args.filter((val: InternalScalarValue) => (typeof val === 'number')) if (values.length === 0) { return new CellError(ErrorType.NUM) diff --git a/src/interpreter/plugin/ModuloPlugin.ts b/src/interpreter/plugin/ModuloPlugin.ts index e5f5ea3e35..cae4bcdbcd 100644 --- a/src/interpreter/plugin/ModuloPlugin.ts +++ b/src/interpreter/plugin/ModuloPlugin.ts @@ -11,15 +11,15 @@ export class ModuloPlugin extends FunctionPlugin { public static implementedFunctions = { 'MOD': { method: 'mod', - parameters: [ + parameters: { list: [ { argumentType: 'number' }, { argumentType: 'number' }, - ], + ]}, }, } public mod(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, ModuloPlugin.implementedFunctions.MOD, (dividend: number, divisor: number) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('MOD'), (dividend: number, divisor: number) => { if (divisor === 0) { return new CellError(ErrorType.DIV_BY_ZERO) } else { diff --git a/src/interpreter/plugin/PowerPlugin.ts b/src/interpreter/plugin/PowerPlugin.ts index 53150065b3..59ed57f72e 100644 --- a/src/interpreter/plugin/PowerPlugin.ts +++ b/src/interpreter/plugin/PowerPlugin.ts @@ -11,14 +11,14 @@ export class PowerPlugin extends FunctionPlugin { public static implementedFunctions = { 'POWER': { method: 'power', - parameters: [ + parameters: { list: [ { argumentType: 'number' }, { argumentType: 'number' }, - ], + ]}, }, } public power(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, PowerPlugin.implementedFunctions.POWER, Math.pow) + return this.runFunction(ast.args, formulaAddress, this.parameters('POWER'), Math.pow) } } diff --git a/src/interpreter/plugin/RadiansPlugin.ts b/src/interpreter/plugin/RadiansPlugin.ts index e673b84b4b..ac0af90e0d 100644 --- a/src/interpreter/plugin/RadiansPlugin.ts +++ b/src/interpreter/plugin/RadiansPlugin.ts @@ -11,14 +11,14 @@ export class RadiansPlugin extends FunctionPlugin { public static implementedFunctions = { 'RADIANS': { method: 'radians', - parameters: [ + parameters: { list: [ { argumentType: 'number' } - ], + ]}, }, } public radians(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, RadiansPlugin.implementedFunctions.RADIANS, + return this.runFunction(ast.args, formulaAddress, this.parameters('RADIANS'), (arg) => arg * (Math.PI / 180) ) } diff --git a/src/interpreter/plugin/RadixConversionPlugin.ts b/src/interpreter/plugin/RadixConversionPlugin.ts index 7ddb7075ff..2bb0235298 100644 --- a/src/interpreter/plugin/RadixConversionPlugin.ts +++ b/src/interpreter/plugin/RadixConversionPlugin.ts @@ -19,82 +19,82 @@ export class RadixConversionPlugin extends FunctionPlugin { public static implementedFunctions = { 'DEC2BIN': { method: 'dec2bin', - parameters: [ + parameters: { list: [ { argumentType: 'number', minValue: minValFromBase(2), maxValue: maxValFromBase(2) }, { argumentType: 'number', optionalArg: true, minValue: 1, maxValue: 10 }, - ], + ]}, }, 'DEC2OCT': { method: 'dec2oct', - parameters: [ + parameters: { list: [ { argumentType: 'number', minValue: minValFromBase(8), maxValue: maxValFromBase(8) }, { argumentType: 'number', optionalArg: true, minValue: 1, maxValue: 10 }, - ], + ]}, }, 'DEC2HEX': { method: 'dec2hex', - parameters: [ + parameters: { list: [ { argumentType: 'number', minValue: minValFromBase(16), maxValue: maxValFromBase(16) }, { argumentType: 'number', optionalArg: true, minValue: 1, maxValue: 10 }, - ], + ]}, }, 'BIN2DEC': { method: 'bin2dec', - parameters: [ + parameters: { list: [ { argumentType: 'string' } - ], + ]}, }, 'BIN2OCT': { method: 'bin2oct', - parameters: [ + parameters: { list: [ { argumentType: 'string' }, { argumentType: 'number', optionalArg: true, minValue: 0, maxValue: DECIMAL_NUMBER_OF_BITS}, - ], + ]}, }, 'BIN2HEX': { method: 'bin2hex', - parameters: [ + parameters: { list: [ { argumentType: 'string' }, { argumentType: 'number', optionalArg: true, minValue: 0, maxValue: DECIMAL_NUMBER_OF_BITS}, - ], + ]}, }, 'DECIMAL': { method: 'decimal', - parameters: [ + parameters: { list: [ { argumentType: 'string' }, { argumentType: 'number', minValue: MIN_BASE, maxValue: MAX_BASE }, - ], + ]}, }, 'BASE': { method: 'base', - parameters: [ + parameters: { list: [ { argumentType: 'number', minValue: 0 }, { argumentType: 'number', minValue: MIN_BASE, maxValue: MAX_BASE }, { argumentType: 'number', optionalArg: true, minValue: 0, maxValue: DECIMAL_NUMBER_OF_BITS}, - ], + ]}, }, } public dec2bin(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, RadixConversionPlugin.implementedFunctions.DEC2BIN, + return this.runFunction(ast.args, formulaAddress, this.parameters('DEC2BIN'), (value, places) => decimalToBaseWithExactPadding(value, 2, places) ) } public dec2oct(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, RadixConversionPlugin.implementedFunctions.DEC2OCT, + return this.runFunction(ast.args, formulaAddress, this.parameters('DEC2OCT'), (value, places) => decimalToBaseWithExactPadding(value, 8, places) ) } public dec2hex(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, RadixConversionPlugin.implementedFunctions.DEC2HEX, + return this.runFunction(ast.args, formulaAddress, this.parameters('DEC2HEX'), (value, places) => decimalToBaseWithExactPadding(value, 16, places) ) } public bin2dec(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, RadixConversionPlugin.implementedFunctions.BIN2DEC, (binary) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('BIN2DEC'), (binary) => { const binaryWithSign = coerceStringToBase(binary, 2, NUMBER_OF_BITS) if(binaryWithSign === undefined) { return new CellError(ErrorType.NUM) @@ -104,7 +104,7 @@ export class RadixConversionPlugin extends FunctionPlugin { } public bin2oct(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, RadixConversionPlugin.implementedFunctions.BIN2OCT, (binary, places) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('BIN2OCT'), (binary, places) => { const binaryWithSign = coerceStringToBase(binary, 2, NUMBER_OF_BITS) if(binaryWithSign === undefined) { return new CellError(ErrorType.NUM) @@ -114,7 +114,7 @@ export class RadixConversionPlugin extends FunctionPlugin { } public bin2hex(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, RadixConversionPlugin.implementedFunctions.BIN2HEX, (binary, places) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('BIN2HEX'), (binary, places) => { const binaryWithSign = coerceStringToBase(binary, 2, NUMBER_OF_BITS) if(binaryWithSign === undefined) { return new CellError(ErrorType.NUM) @@ -124,11 +124,11 @@ export class RadixConversionPlugin extends FunctionPlugin { } public base(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, RadixConversionPlugin.implementedFunctions.BASE, decimalToBaseWithMinimumPadding) + return this.runFunction(ast.args, formulaAddress, this.parameters('BASE'), decimalToBaseWithMinimumPadding) } public decimal(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, RadixConversionPlugin.implementedFunctions.DECIMAL, (arg, base) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('DECIMAL'), (arg, base) => { const input = coerceStringToBase(arg, base, DECIMAL_NUMBER_OF_BITS) if(input === undefined) { return new CellError(ErrorType.NUM) diff --git a/src/interpreter/plugin/RandomPlugin.ts b/src/interpreter/plugin/RandomPlugin.ts index f85a65c562..8fa32c1333 100644 --- a/src/interpreter/plugin/RandomPlugin.ts +++ b/src/interpreter/plugin/RandomPlugin.ts @@ -3,15 +3,15 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' -import {AstNodeType, ProcedureAst} from '../../parser' +import {InternalScalarValue, SimpleCellAddress} from '../../Cell' +import {ProcedureAst} from '../../parser' import {FunctionPlugin} from './FunctionPlugin' export class RandomPlugin extends FunctionPlugin { public static implementedFunctions = { 'RAND': { method: 'rand', - parameters: [], + parameters: { list: [] }, isVolatile: true, }, } @@ -26,6 +26,6 @@ export class RandomPlugin extends FunctionPlugin { * @param formulaAddress */ public rand(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, RandomPlugin.implementedFunctions.RAND, Math.random) + return this.runFunction(ast.args, formulaAddress, this.parameters('RAND'), Math.random) } } diff --git a/src/interpreter/plugin/RoundingPlugin.ts b/src/interpreter/plugin/RoundingPlugin.ts index b714c2c6f0..6ffa0c2444 100644 --- a/src/interpreter/plugin/RoundingPlugin.ts +++ b/src/interpreter/plugin/RoundingPlugin.ts @@ -21,62 +21,62 @@ export class RoundingPlugin extends FunctionPlugin { public static implementedFunctions = { 'ROUNDUP': { method: 'roundup', - parameters: [ + parameters: { list: [ { argumentType: 'number' }, { argumentType: 'number', defaultValue: 0}, - ], + ]}, }, 'ROUNDDOWN': { method: 'rounddown', - parameters: [ + parameters: { list: [ { argumentType: 'number' }, { argumentType: 'number', defaultValue: 0}, - ], + ]}, }, 'ROUND': { method: 'round', - parameters: [ + parameters: { list: [ { argumentType: 'number' }, { argumentType: 'number', defaultValue: 0}, - ], + ]}, }, 'TRUNC': { method: 'trunc', - parameters: [ + parameters: { list: [ { argumentType: 'number' }, { argumentType: 'number', defaultValue: 0}, - ], + ]}, }, 'INT': { method: 'intFunc', - parameters: [ + parameters: { list: [ { argumentType: 'number' } - ], + ]}, }, 'EVEN': { method: 'even', - parameters: [ + parameters: { list: [ { argumentType: 'number' } - ], + ]}, }, 'ODD': { method: 'odd', - parameters: [ + parameters: { list: [ { argumentType: 'number' } - ], + ]}, }, 'CEILING': { method: 'ceiling', - parameters: [ + parameters: { list: [ { argumentType: 'number' }, { argumentType: 'number', defaultValue: 1 }, { argumentType: 'number', defaultValue: 0 }, - ], + ]}, }, } public roundup(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, RoundingPlugin.implementedFunctions.ROUNDDOWN, (numberToRound: number, places: number): number => { + return this.runFunction(ast.args, formulaAddress, this.parameters('ROUNDDOWN'), (numberToRound: number, places: number): number => { const placesMultiplier = Math.pow(10, places) if (numberToRound < 0) { return -Math.ceil(-numberToRound * placesMultiplier) / placesMultiplier @@ -87,7 +87,7 @@ export class RoundingPlugin extends FunctionPlugin { } public rounddown(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, RoundingPlugin.implementedFunctions.ROUNDDOWN, (numberToRound: number, places: number): number => { + return this.runFunction(ast.args, formulaAddress, this.parameters('ROUNDDOWN'), (numberToRound: number, places: number): number => { const placesMultiplier = Math.pow(10, places) if (numberToRound < 0) { return -Math.floor(-numberToRound * placesMultiplier) / placesMultiplier @@ -98,7 +98,7 @@ export class RoundingPlugin extends FunctionPlugin { } public round(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, RoundingPlugin.implementedFunctions.ROUND, (numberToRound: number, places: number): number => { + return this.runFunction(ast.args, formulaAddress, this.parameters('ROUND'), (numberToRound: number, places: number): number => { const placesMultiplier = Math.pow(10, places) if (numberToRound < 0) { return -Math.round(-numberToRound * placesMultiplier) / placesMultiplier @@ -113,7 +113,7 @@ export class RoundingPlugin extends FunctionPlugin { } public intFunc(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, RoundingPlugin.implementedFunctions.INT, (coercedNumberToRound) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('INT'), (coercedNumberToRound) => { if (coercedNumberToRound < 0) { return -Math.floor(-coercedNumberToRound) } else { @@ -123,7 +123,7 @@ export class RoundingPlugin extends FunctionPlugin { } public even(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, RoundingPlugin.implementedFunctions.EVEN, (coercedNumberToRound) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('EVEN'), (coercedNumberToRound) => { if (coercedNumberToRound < 0) { return -findNextEvenNumber(-coercedNumberToRound) } else { @@ -133,7 +133,7 @@ export class RoundingPlugin extends FunctionPlugin { } public odd(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, RoundingPlugin.implementedFunctions.ODD, (coercedNumberToRound) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('ODD'), (coercedNumberToRound) => { if (coercedNumberToRound < 0) { return -findNextOddNumber(-coercedNumberToRound) } else { @@ -143,7 +143,7 @@ export class RoundingPlugin extends FunctionPlugin { } public ceiling(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, RoundingPlugin.implementedFunctions.CEILING, (value: number, significance: number, mode: number) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('CEILING'), (value: number, significance: number, mode: number) => { if (significance === 0 || value === 0) { return 0 } diff --git a/src/interpreter/plugin/SqrtPlugin.ts b/src/interpreter/plugin/SqrtPlugin.ts index 7ca2c1ab4b..f1b0174de2 100644 --- a/src/interpreter/plugin/SqrtPlugin.ts +++ b/src/interpreter/plugin/SqrtPlugin.ts @@ -11,13 +11,13 @@ export class SqrtPlugin extends FunctionPlugin { public static implementedFunctions = { 'SQRT': { method: 'sqrt', - parameters: [ + parameters: { list: [ { argumentType: 'number' } - ], + ]}, }, } public sqrt(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, SqrtPlugin.implementedFunctions.SQRT, Math.sqrt) + return this.runFunction(ast.args, formulaAddress, this.parameters('SQRT'), Math.sqrt) } } diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index fb6588beaf..4e758b5ba6 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -14,79 +14,101 @@ export class TextPlugin extends FunctionPlugin { public static implementedFunctions = { 'CONCATENATE': { method: 'concatenate', - parameters: [ - { argumentType: 'string'} - ], - repeatedArg: true, - expandRanges: true, + parameters: { + list: [ + {argumentType: 'string'} + ], + repeatedArg: true, + expandRanges: true, + }, }, 'SPLIT': { method: 'split', - parameters: [ - { argumentType: 'string' }, - { argumentType: 'number' }, - ], + parameters: { + list: [ + {argumentType: 'string'}, + {argumentType: 'number'}, + ] + }, }, 'LEN': { method: 'len', - parameters: [ - { argumentType: 'string'} - ], + parameters: { + list: [ + {argumentType: 'string'} + ] + }, }, 'TRIM': { method: 'trim', - parameters: [ - { argumentType: 'string'} - ], + parameters: { + list: [ + {argumentType: 'string'} + ] + }, }, 'PROPER': { method: 'proper', - parameters: [ - { argumentType: 'string'} - ], + parameters: { + list: [ + {argumentType: 'string'} + ] + }, }, 'CLEAN': { method: 'clean', - parameters: [ - { argumentType: 'string'} - ], + parameters: { + list: [ + {argumentType: 'string'} + ] + }, }, 'REPT': { method: 'rept', - parameters: [ - { argumentType: 'string' }, - { argumentType: 'number' }, - ], + parameters: { + list: [ + {argumentType: 'string'}, + {argumentType: 'number'}, + ] + }, }, 'RIGHT': { method: 'right', - parameters: [ - { argumentType: 'string' }, - { argumentType: 'number', defaultValue: 1 }, - ], + parameters: { + list: [ + {argumentType: 'string'}, + {argumentType: 'number', defaultValue: 1}, + ] + }, }, 'LEFT': { method: 'left', - parameters: [ - { argumentType: 'string' }, - { argumentType: 'number', defaultValue: 1 }, - ], + parameters: { + list: [ + {argumentType: 'string'}, + {argumentType: 'number', defaultValue: 1}, + ] + }, }, 'SEARCH': { method: 'search', - parameters: [ - { argumentType: 'string' }, - { argumentType: 'string' }, - { argumentType: 'number', defaultValue: 1 }, - ], + parameters: { + list: [ + {argumentType: 'string'}, + {argumentType: 'string'}, + {argumentType: 'number', defaultValue: 1}, + ] + }, }, 'FIND': { method: 'find', - parameters: [ - { argumentType: 'string' }, - { argumentType: 'string' }, - { argumentType: 'number', defaultValue: 1 }, - ], + parameters: { + list: [ + {argumentType: 'string'}, + {argumentType: 'string'}, + {argumentType: 'number', defaultValue: 1}, + ] + }, } } @@ -99,7 +121,7 @@ export class TextPlugin extends FunctionPlugin { * @param formulaAddress */ public concatenate(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.CONCATENATE, (...args) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('CONCATENATE'), (...args) => { return ''.concat(...args) }) } @@ -113,7 +135,7 @@ export class TextPlugin extends FunctionPlugin { * @param formulaAddress */ public split(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.SPLIT, (stringToSplit: string, indexToUse: number) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('SPLIT'), (stringToSplit: string, indexToUse: number) => { const splittedString = stringToSplit.split(' ') if (indexToUse >= splittedString.length || indexToUse < 0) { @@ -125,32 +147,32 @@ export class TextPlugin extends FunctionPlugin { } public len(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.LEN, (arg: string) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('LEN'), (arg: string) => { return arg.length }) } public trim(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.TRIM, (arg: string) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('TRIM'), (arg: string) => { return arg.replace(/^ +| +$/g, '').replace(/ +/g, ' ') }) } public proper(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.PROPER, (arg: string) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('PROPER'), (arg: string) => { return arg.replace(/\w\S*/g, word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase()) }) } public clean(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.CLEAN, (arg: string) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('CLEAN'), (arg: string) => { // eslint-disable-next-line no-control-regex return arg.replace(/[\u0000-\u001F]/g, '') }) } public rept(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.REPT, (text: string, count: number) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('REPT'), (text: string, count: number) => { if (count < 0) { return new CellError(ErrorType.VALUE) } @@ -159,7 +181,7 @@ export class TextPlugin extends FunctionPlugin { } public right(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.RIGHT, (text: string, length: number) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('RIGHT'), (text: string, length: number) => { if (length < 0) { return new CellError(ErrorType.VALUE) } else if (length === 0) { @@ -170,7 +192,7 @@ export class TextPlugin extends FunctionPlugin { } public left(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.LEFT, (text: string, length: number) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('LEFT'), (text: string, length: number) => { if (length < 0) { return new CellError(ErrorType.VALUE) } @@ -179,7 +201,7 @@ export class TextPlugin extends FunctionPlugin { } public search(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.SEARCH, (pattern, text: string, startIndex: number) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('SEARCH'), (pattern, text: string, startIndex: number) => { if (startIndex < 1 || startIndex > text.length) { return new CellError(ErrorType.VALUE) } @@ -199,7 +221,7 @@ export class TextPlugin extends FunctionPlugin { } public find(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, TextPlugin.implementedFunctions.FIND, (pattern, text: string, startIndex: number) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('FIND'), (pattern, text: string, startIndex: number) => { if (startIndex < 1 || startIndex > text.length) { return new CellError(ErrorType.VALUE) } diff --git a/src/interpreter/plugin/TrigonometryPlugin.ts b/src/interpreter/plugin/TrigonometryPlugin.ts index 4b6365f125..e3615c4db7 100644 --- a/src/interpreter/plugin/TrigonometryPlugin.ts +++ b/src/interpreter/plugin/TrigonometryPlugin.ts @@ -15,52 +15,52 @@ export class TrigonometryPlugin extends FunctionPlugin { public static implementedFunctions = { 'ACOS': { method: 'acos', - parameters: [ + parameters: { list: [ { argumentType: 'number' } - ], + ]} }, 'ASIN': { method: 'asin', - parameters: [ + parameters: { list: [ { argumentType: 'number' } - ], + ]} }, 'COS': { method: 'cos', - parameters: [ + parameters: { list: [ { argumentType: 'number' } - ], + ]} }, 'SIN': { method: 'sin', - parameters: [ + parameters: { list: [ { argumentType: 'number' } - ], + ]} }, 'TAN': { method: 'tan', - parameters: [ + parameters: { list: [ { argumentType: 'number' } - ], + ]} }, 'ATAN': { method: 'atan', - parameters: [ + parameters: { list: [ { argumentType: 'number' } - ], + ]} }, 'ATAN2': { method: 'atan2', - parameters: [ + parameters: { list: [ { argumentType: 'number' }, { argumentType: 'number' }, - ], + ]} }, 'COT': { method: 'ctg', - parameters: [ + parameters: { list: [ { argumentType: 'number' } - ], + ]} }, } @@ -73,35 +73,35 @@ export class TrigonometryPlugin extends FunctionPlugin { * @param formulaAddress */ public acos(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, TrigonometryPlugin.implementedFunctions.ACOS, Math.acos) + return this.runFunction(ast.args, formulaAddress, this.parameters('ACOS'), Math.acos) } public asin(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, TrigonometryPlugin.implementedFunctions.ASIN, Math.asin) + return this.runFunction(ast.args, formulaAddress, this.parameters('ASIN'), Math.asin) } public cos(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, TrigonometryPlugin.implementedFunctions.COS, Math.cos) + return this.runFunction(ast.args, formulaAddress, this.parameters('COS'), Math.cos) } public sin(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, TrigonometryPlugin.implementedFunctions.SIN, Math.sin) + return this.runFunction(ast.args, formulaAddress, this.parameters('SIN'), Math.sin) } public tan(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, TrigonometryPlugin.implementedFunctions.TAN, Math.tan) + return this.runFunction(ast.args, formulaAddress, this.parameters('TAN'), Math.tan) } public atan(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, TrigonometryPlugin.implementedFunctions.ATAN, Math.atan) + return this.runFunction(ast.args, formulaAddress, this.parameters('ATAN'), Math.atan) } public atan2(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, TrigonometryPlugin.implementedFunctions.ATAN2, Math.atan2) + return this.runFunction(ast.args, formulaAddress, this.parameters('ATAN2'), Math.atan2) } public ctg(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, TrigonometryPlugin.implementedFunctions.COT, (coercedArg) => { + return this.runFunction(ast.args, formulaAddress, this.parameters('COT'), (coercedArg) => { if (coercedArg === 0) { return new CellError(ErrorType.DIV_BY_ZERO) } else { From 86a203cd0715c5bd3d6192c98c489216064ea15c Mon Sep 17 00:00:00 2001 From: izulin Date: Mon, 27 Jul 2020 16:36:56 +0200 Subject: [PATCH 59/97] . --- src/interpreter/plugin/FunctionPlugin.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index d74ec3b99b..d0781dd567 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -13,10 +13,7 @@ import {Ast, ProcedureAst} from '../../parser' import {coerceScalarToBoolean, coerceScalarToString} from '../ArithmeticHelper' import {Interpreter} from '../Interpreter' import {InterpreterValue, SimpleRangeValue} from '../InterpreterValue' -<<<<<<< HEAD -======= import {Serialization} from '../../Serialization' ->>>>>>> develop export interface ImplementedFunctions { [formulaId: string]: FunctionMetadata, @@ -222,4 +219,4 @@ export abstract class FunctionPlugin { } throw new Error('FIXME Should not be undefined') } -} \ No newline at end of file +} From 25ec51e7ef39926ac4c1e84cc041aa3f088b3357 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Tue, 28 Jul 2020 17:53:47 +0200 Subject: [PATCH 60/97] Median benchmark --- test/performance/1-basic.ts | 23 ++++++++++---------- test/performance/sheets/median-evaluation.ts | 20 +++++++++++++++++ 2 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 test/performance/sheets/median-evaluation.ts diff --git a/test/performance/1-basic.ts b/test/performance/1-basic.ts index e494e7db4a..ec72d88dfc 100644 --- a/test/performance/1-basic.ts +++ b/test/performance/1-basic.ts @@ -2,23 +2,24 @@ import {batch, benchmark, BenchmarkResult} from './benchmark' import {expectedValues as expectedValuesT, sheet as sheetTGenerator} from './sheets/05-sheet-t' import {expectedValues as expectedValuesA, sheet as sheetAGenerator} from './sheets/09-sheet-a' import {expectedValues as expectedValuesB, sheet as sheetBGenerator} from './sheets/10-sheet-b' +import {sheet as median} from './sheets/median-evaluation' import {sheet as columnRangesGenerator} from './sheets/column-ranges' (() => { - const sheetA = sheetAGenerator() - const sheetB = sheetBGenerator() - const sheetT = sheetTGenerator() - const infiniteRanges = columnRangesGenerator() + // const sheetA = sheetAGenerator() + // const sheetB = sheetBGenerator() + // const sheetT = sheetTGenerator() + // const infiniteRanges = columnRangesGenerator() const result: BenchmarkResult[] = [] batch(result, - () => benchmark('Sheet A', sheetA, expectedValuesA(sheetA), {numberOfRuns: 10}), - () => benchmark('Sheet B', sheetB, expectedValuesB(sheetB), {numberOfRuns: 10}), - () => benchmark('Sheet T', sheetT, expectedValuesT(sheetT), {numberOfRuns: 10}), - () => benchmark('Column ranges', infiniteRanges, [{ - address: 'AX50', - value: 1.04519967355127e+63 - }], {expectedTime: 1000, numberOfRuns: 10}) + () => benchmark('Sheet A', median(), [], {numberOfRuns: 10}), + // () => benchmark('Sheet B', sheetB, expectedValuesB(sheetB), {numberOfRuns: 10}), + // () => benchmark('Sheet T', sheetT, expectedValuesT(sheetT), {numberOfRuns: 10}), + // () => benchmark('Column ranges', infiniteRanges, [{ + // address: 'AX50', + // value: 1.04519967355127e+63 + // }], {expectedTime: 1000, numberOfRuns: 10}) ) console.table(result.map(e => ({ diff --git a/test/performance/sheets/median-evaluation.ts b/test/performance/sheets/median-evaluation.ts new file mode 100644 index 0000000000..a9f890d65d --- /dev/null +++ b/test/performance/sheets/median-evaluation.ts @@ -0,0 +1,20 @@ +export function sheet(rows: number = 20000) { + const sheet = [] + sheet.push(['1', '2', '3', '0', '0']) + + let prev = 1 + + while (prev < rows) { + const rowToPush = [ + `${prev + 1}`, + '2', + '=3*5', + `=A${prev}+D${prev}`, + `=OR(FALSE(), ISEVEN(MEDIAN(A${prev},B${prev},C${prev},D${prev})))`, + ] + + sheet.push(rowToPush) + ++prev + } + return sheet +} \ No newline at end of file From 5644dd5168150f6832fd89a25549a69c64ebb23e Mon Sep 17 00:00:00 2001 From: izulin Date: Tue, 28 Jul 2020 18:50:41 +0200 Subject: [PATCH 61/97] faster benchmark --- src/interpreter/CriterionFunctionCompute.ts | 2 +- src/interpreter/InterpreterValue.ts | 37 ++++++++++++++++++--- src/interpreter/plugin/CorrelPlugin.ts | 4 +-- src/interpreter/plugin/FunctionPlugin.ts | 19 +++++++++-- src/interpreter/plugin/SumprodPlugin.ts | 4 +-- 5 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/interpreter/CriterionFunctionCompute.ts b/src/interpreter/CriterionFunctionCompute.ts index 1a58fec692..89d61c9841 100644 --- a/src/interpreter/CriterionFunctionCompute.ts +++ b/src/interpreter/CriterionFunctionCompute.ts @@ -108,7 +108,7 @@ export class CriterionFunctionCompute { private evaluateRangeValue(simpleValuesRange: SimpleRangeValue, conditions: Condition[]) { const criterionLambdas = conditions.map((condition) => condition.criterionPackage.lambda) const values = Array.from(simpleValuesRange.valuesFromTopLeftCorner()).map(this.mapFunction)[Symbol.iterator]() - const conditionsIterators = conditions.map((condition) => condition.conditionRange.valuesFromTopLeftCorner()) + const conditionsIterators = conditions.map((condition) => condition.conditionRange.iterateValuesFromTopLeftCorner()) const filteredValues = ifFilter(criterionLambdas, conditionsIterators, values) return this.reduceFunction(filteredValues) } diff --git a/src/interpreter/InterpreterValue.ts b/src/interpreter/InterpreterValue.ts index 614da03551..fbcf42811f 100644 --- a/src/interpreter/InterpreterValue.ts +++ b/src/interpreter/InterpreterValue.ts @@ -25,7 +25,17 @@ export class ArrayData { return this._hasOnlyNumbers } - public* valuesFromTopLeftCorner(): IterableIterator { + public valuesFromTopLeftCorner(): InternalScalarValue[] { + const ret = [] + for (let i = 0; i < this.size.height; i++) { + for (let j = 0; j < this.size.width; j++) { + ret.push(this.data[i][j]) + } + } + return ret + } + + public* iterateValuesFromTopLeftCorner(): IterableIterator { for (let i = 0; i < this.size.height; i++) { for (let j = 0; j < this.size.width; j++) { yield this.data[i][j] @@ -76,7 +86,7 @@ export class OnlyRangeData { this.ensureThatComputed() if (this._hasOnlyNumbers === undefined) { - for (const v of this.valuesFromTopLeftCorner()) { + for (const v of this.iterateValuesFromTopLeftCorner()) { if (typeof v !== 'number') { this._hasOnlyNumbers = false break @@ -92,7 +102,20 @@ export class OnlyRangeData { return this._range } - public* valuesFromTopLeftCorner(): IterableIterator { + public valuesFromTopLeftCorner(): InternalScalarValue[] { + this.ensureThatComputed() + + const ret = [] + for (let i = 0; i < this.data!.length; i++) { + for (let j = 0; j < this.data![0].length; j++) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + ret.push(this.data![i][j]) + } + } + return ret + } + + public* iterateValuesFromTopLeftCorner(): IterableIterator { this.ensureThatComputed() for (let i = 0; i < this.data!.length; i++) { @@ -178,8 +201,12 @@ export class SimpleRangeValue { return this.data.raw() } - public* valuesFromTopLeftCorner(): IterableIterator { - yield *this.data.valuesFromTopLeftCorner() + public valuesFromTopLeftCorner(): InternalScalarValue[] { + return this.data.valuesFromTopLeftCorner() + } + + public* iterateValuesFromTopLeftCorner(): IterableIterator { + yield *this.data.iterateValuesFromTopLeftCorner() } public numberOfElements(): number { diff --git a/src/interpreter/plugin/CorrelPlugin.ts b/src/interpreter/plugin/CorrelPlugin.ts index 6992c923b8..4020724bcc 100644 --- a/src/interpreter/plugin/CorrelPlugin.ts +++ b/src/interpreter/plugin/CorrelPlugin.ts @@ -39,8 +39,8 @@ export class CorrelPlugin extends FunctionPlugin { } private computePearson(dataX: SimpleRangeValue, dataY: SimpleRangeValue): number | CellError { - const xit = dataX.valuesFromTopLeftCorner() - const yit = dataY.valuesFromTopLeftCorner() + const xit = dataX.iterateValuesFromTopLeftCorner() + const yit = dataY.iterateValuesFromTopLeftCorner() let x, y let count = 0 diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index d0781dd567..b41a3ae8c3 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -85,7 +85,7 @@ export abstract class FunctionPlugin { for (const argAst of asts) { const value = this.evaluateAst(argAst, formulaAddress) if (value instanceof SimpleRangeValue) { - for (const scalarValue of value.valuesFromTopLeftCorner()) { + for (const scalarValue of value.iterateValuesFromTopLeftCorner()) { yield [scalarValue, true] } } else { @@ -94,6 +94,21 @@ export abstract class FunctionPlugin { } } + protected listOfScalarValues(asts: Ast[], formulaAddress: SimpleCellAddress): [InternalScalarValue, boolean][] { + const ret: [InternalScalarValue, boolean][] = [] + for (const argAst of asts) { + const value = this.evaluateAst(argAst, formulaAddress) + if (value instanceof SimpleRangeValue) { + for (const scalarValue of value.valuesFromTopLeftCorner()) { + ret.push([scalarValue, true]) + } + } else { + ret.push([value,false]) + } + } + return ret + } + protected computeListOfValuesInRange(range: AbsoluteCellRange): InternalScalarValue[] { const values: InternalScalarValue[] = [] for (const cellFromRange of range.addresses(this.dependencyGraph)) { @@ -162,7 +177,7 @@ export abstract class FunctionPlugin { let scalarValues: [InterpreterValue, boolean][] if(functionDefinition.expandRanges) { - scalarValues = [...this.iterateOverScalarValues(args, formulaAddress)] + scalarValues = this.listOfScalarValues(args, formulaAddress) } else { scalarValues = args.map((ast) => [this.evaluateAst(ast, formulaAddress), false]) } diff --git a/src/interpreter/plugin/SumprodPlugin.ts b/src/interpreter/plugin/SumprodPlugin.ts index 76ab5e861b..f5ed723e28 100644 --- a/src/interpreter/plugin/SumprodPlugin.ts +++ b/src/interpreter/plugin/SumprodPlugin.ts @@ -38,8 +38,8 @@ export class SumprodPlugin extends FunctionPlugin { private reduceSumprod(left: SimpleRangeValue, right: SimpleRangeValue): number | CellError { let result = 0 - const lit = left.valuesFromTopLeftCorner() - const rit = right.valuesFromTopLeftCorner() + const lit = left.iterateValuesFromTopLeftCorner() + const rit = right.iterateValuesFromTopLeftCorner() let l, r while (l = lit.next(), r = rit.next(), !l.done && !r.done) { From 6d050358150ae5deee4da37e861513fcd2fe67c3 Mon Sep 17 00:00:00 2001 From: izulin Date: Tue, 28 Jul 2020 19:39:49 +0200 Subject: [PATCH 62/97] fix --- src/interpreter/plugin/FunctionPlugin.ts | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index b41a3ae8c3..76d4a436a2 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -81,19 +81,6 @@ export abstract class FunctionPlugin { return this.interpreter.evaluateAst(ast, formulaAddress) } - protected* iterateOverScalarValues(asts: Ast[], formulaAddress: SimpleCellAddress): IterableIterator<[InternalScalarValue, boolean]> { - for (const argAst of asts) { - const value = this.evaluateAst(argAst, formulaAddress) - if (value instanceof SimpleRangeValue) { - for (const scalarValue of value.iterateValuesFromTopLeftCorner()) { - yield [scalarValue, true] - } - } else { - yield [value, false] - } - } - } - protected listOfScalarValues(asts: Ast[], formulaAddress: SimpleCellAddress): [InternalScalarValue, boolean][] { const ret: [InternalScalarValue, boolean][] = [] for (const argAst of asts) { @@ -103,7 +90,7 @@ export abstract class FunctionPlugin { ret.push([scalarValue, true]) } } else { - ret.push([value,false]) + ret.push([value, false]) } } return ret From 53c955d31db9d3aa955fc359a43feb5d1e59c90c Mon Sep 17 00:00:00 2001 From: izulin Date: Wed, 29 Jul 2020 12:14:31 +0200 Subject: [PATCH 63/97] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90815da2f3..8a8a2879d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Operation `moveCells` creating cyclic dependencies does not cause losing original formula. (#479) +- Simplified adding new function modules, reworked (simplified) implementations of existing modules. ### Fixed - Fixed hardcoding of languages in i18n tests. (#471) From 473fe33808f853a4b800855f7c11a093af8709e2 Mon Sep 17 00:00:00 2001 From: izulin Date: Wed, 29 Jul 2020 14:48:51 +0200 Subject: [PATCH 64/97] refactor --- src/interpreter/plugin/DatePlugin.ts | 236 ++++++++--------------- src/interpreter/plugin/FunctionPlugin.ts | 38 ++-- 2 files changed, 93 insertions(+), 181 deletions(-) diff --git a/src/interpreter/plugin/DatePlugin.ts b/src/interpreter/plugin/DatePlugin.ts index 558a2030e8..e37ac1bcb6 100644 --- a/src/interpreter/plugin/DatePlugin.ts +++ b/src/interpreter/plugin/DatePlugin.ts @@ -16,25 +16,65 @@ import {FunctionPlugin} from './FunctionPlugin' export class DatePlugin extends FunctionPlugin { public static implementedFunctions = { 'DATE': { - method: 'date' + method: 'date', + parameters: { + list: [ + {argumentType: 'number'}, + {argumentType: 'number'}, + {argumentType: 'number'}, + ] + }, }, 'MONTH': { - method: 'month' + method: 'month', + parameters: { + list: [ + {argumentType: 'number'}, + ] + }, }, 'YEAR': { - method: 'year' + method: 'year', + parameters: { + list: [ + {argumentType: 'number'}, + ] + }, }, 'TEXT': { - method: 'text' + method: 'text', + parameters: { + list: [ + {argumentType: 'number'}, + {argumentType: 'string'}, + ] + }, }, 'EOMONTH': { - method: 'eomonth' + method: 'eomonth', + parameters: { + list: [ + {argumentType: 'number'}, + {argumentType: 'number'}, + ] + }, }, 'DAY': { - method: 'day' + method: 'day', + parameters: { + list: [ + {argumentType: 'number'}, + ] + }, }, 'DAYS': { - method: 'days' + method: 'days', + parameters: { + list: [ + {argumentType: 'number'}, + {argumentType: 'number'}, + ] + }, }, } @@ -47,131 +87,43 @@ export class DatePlugin extends FunctionPlugin { * @param formulaAddress */ public date(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length !== 3) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - const year = this.evaluateAst(ast.args[0], formulaAddress) - const month = this.evaluateAst(ast.args[1], formulaAddress) - const day = this.evaluateAst(ast.args[2], formulaAddress) - if (year instanceof SimpleRangeValue || month instanceof SimpleRangeValue || day instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - const coercedYear = this.coerceScalarToNumberOrError(year) - const coercedMonth = this.coerceScalarToNumberOrError(month) - const coercedDay = this.coerceScalarToNumberOrError(day) - - if (coercedYear instanceof CellError) { - return coercedYear - } - - if (coercedMonth instanceof CellError) { - return coercedMonth - } - - if (coercedDay instanceof CellError) { - return coercedDay - } - const d = Math.trunc(coercedDay) - let m = Math.trunc(coercedMonth) - let y = Math.trunc(coercedYear) - if (y < this.interpreter.dateHelper.getEpochYearZero()) { - y += this.interpreter.dateHelper.getEpochYearZero() - } - const delta = Math.floor((m - 1) / 12) - y += delta - m -= delta * 12 - - const date = {year: y, month: m, day: 1} - if (this.interpreter.dateHelper.isValidDate(date)) { - const ret = this.interpreter.dateHelper.dateToNumber(date) + (d - 1) - if (this.interpreter.dateHelper.getWithinBounds(ret)) { - return ret + return this.runFunction(ast.args, formulaAddress, this.parameters('DATE'), (year, month, day) => { + const d = Math.trunc(day) + let m = Math.trunc(month) + let y = Math.trunc(year) + if (y < this.interpreter.dateHelper.getEpochYearZero()) { + y += this.interpreter.dateHelper.getEpochYearZero() } - } - return new CellError(ErrorType.VALUE) + const delta = Math.floor((m - 1) / 12) + y += delta + m -= delta * 12 + + const date = {year: y, month: m, day: 1} + if (this.interpreter.dateHelper.isValidDate(date)) { + const ret = this.interpreter.dateHelper.dateToNumber(date) + (d - 1) + if (this.interpreter.dateHelper.getWithinBounds(ret)) { + return ret + } + } + return new CellError(ErrorType.VALUE) + }) } public eomonth(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length !== 2) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - const arg = this.evaluateAst(ast.args[0], formulaAddress) - if (arg instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - const dateNumber = this.coerceScalarToNumberOrError(arg) - if (dateNumber instanceof CellError) { - return dateNumber - } - - const numberOfMonthsToShiftValue = this.evaluateAst(ast.args[1], formulaAddress) - if (numberOfMonthsToShiftValue instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - const numberOfMonthsToShift = this.coerceScalarToNumberOrError(numberOfMonthsToShiftValue) - if (numberOfMonthsToShift instanceof CellError) { - return numberOfMonthsToShift - } - - const date = this.interpreter.dateHelper.numberToSimpleDate(dateNumber) - return this.interpreter.dateHelper.dateToNumber(endOfMonth(offsetMonth(date, numberOfMonthsToShift))) + return this.runFunction(ast.args, formulaAddress, this.parameters('EOMONTH'), (dateNumber, numberOfMonthsToShift) => { + const date = this.interpreter.dateHelper.numberToSimpleDate(dateNumber) + return this.interpreter.dateHelper.dateToNumber(endOfMonth(offsetMonth(date, numberOfMonthsToShift))) + }) } public day(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length !== 1) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - const arg = this.evaluateAst(ast.args[0], formulaAddress) - if (arg instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - const dateNumber = this.coerceScalarToNumberOrError(arg) - if (dateNumber instanceof CellError) { - return dateNumber - } - return this.interpreter.dateHelper.numberToSimpleDate(dateNumber).day + return this.runFunction(ast.args, formulaAddress, this.parameters('DAY'), + (dateNumber) => this.interpreter.dateHelper.numberToSimpleDate(dateNumber).day + ) } public days(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length !== 2) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - const endDate = this.evaluateAst(ast.args[0], formulaAddress) - if (endDate instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - const endDateNumber = this.coerceScalarToNumberOrError(endDate) - if (endDateNumber instanceof CellError) { - return endDateNumber - } - - const startDate = this.evaluateAst(ast.args[1], formulaAddress) - if (startDate instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - const startDateNumber = this.coerceScalarToNumberOrError(startDate) - if (startDateNumber instanceof CellError) { - return startDateNumber - } - - return endDateNumber - startDateNumber + return this.runFunction(ast.args, formulaAddress, this.parameters('DAYS'), (endDate, startDate) => endDate - startDate) } /** @@ -183,23 +135,9 @@ export class DatePlugin extends FunctionPlugin { * @param formulaAddress */ public month(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length !== 1) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - const arg = this.evaluateAst(ast.args[0], formulaAddress) - if (arg instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - const dateNumber = this.coerceScalarToNumberOrError(arg) - if (dateNumber instanceof CellError) { - return dateNumber - } - - return this.interpreter.dateHelper.numberToSimpleDate(dateNumber).month + return this.runFunction(ast.args, formulaAddress, this.parameters('MONTH'), + (dateNumber) => this.interpreter.dateHelper.numberToSimpleDate(dateNumber).month + ) } /** @@ -211,23 +149,9 @@ export class DatePlugin extends FunctionPlugin { * @param formulaAddress */ public year(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length !== 1) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - const arg = this.evaluateAst(ast.args[0], formulaAddress) - if (arg instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - const dateNumber = this.coerceScalarToNumberOrError(arg) - if (dateNumber instanceof CellError) { - return dateNumber - } - - return this.interpreter.dateHelper.numberToSimpleDate(dateNumber).year + return this.runFunction(ast.args, formulaAddress, this.parameters('YEAR'), + (dateNumber) => this.interpreter.dateHelper.numberToSimpleDate(dateNumber).year + ) } /** diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 76d4a436a2..5afb3cd6a4 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -172,43 +172,31 @@ export abstract class FunctionPlugin { const coercedArguments: Maybe[] = [] let argCoerceFailure: Maybe = undefined - let j = 0 let i = 0 - while(i Date: Wed, 29 Jul 2020 14:56:25 +0200 Subject: [PATCH 65/97] docs --- src/interpreter/plugin/FunctionPlugin.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 5afb3cd6a4..bec7d10ffd 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -177,6 +177,8 @@ export abstract class FunctionPlugin { return new CellError(ErrorType.NA) } for(let i=0; i Date: Wed, 29 Jul 2020 15:03:00 +0200 Subject: [PATCH 66/97] linter --- src/interpreter/plugin/FunctionPlugin.ts | 1 - test/performance/1-basic.ts | 23 +++++++++++------------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index bec7d10ffd..14235e3558 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -172,7 +172,6 @@ export abstract class FunctionPlugin { const coercedArguments: Maybe[] = [] let argCoerceFailure: Maybe = undefined - let i = 0 if(!functionDefinition.repeatedArg && argumentDefinitions.length < scalarValues.length) { return new CellError(ErrorType.NA) } diff --git a/test/performance/1-basic.ts b/test/performance/1-basic.ts index ec72d88dfc..e494e7db4a 100644 --- a/test/performance/1-basic.ts +++ b/test/performance/1-basic.ts @@ -2,24 +2,23 @@ import {batch, benchmark, BenchmarkResult} from './benchmark' import {expectedValues as expectedValuesT, sheet as sheetTGenerator} from './sheets/05-sheet-t' import {expectedValues as expectedValuesA, sheet as sheetAGenerator} from './sheets/09-sheet-a' import {expectedValues as expectedValuesB, sheet as sheetBGenerator} from './sheets/10-sheet-b' -import {sheet as median} from './sheets/median-evaluation' import {sheet as columnRangesGenerator} from './sheets/column-ranges' (() => { - // const sheetA = sheetAGenerator() - // const sheetB = sheetBGenerator() - // const sheetT = sheetTGenerator() - // const infiniteRanges = columnRangesGenerator() + const sheetA = sheetAGenerator() + const sheetB = sheetBGenerator() + const sheetT = sheetTGenerator() + const infiniteRanges = columnRangesGenerator() const result: BenchmarkResult[] = [] batch(result, - () => benchmark('Sheet A', median(), [], {numberOfRuns: 10}), - // () => benchmark('Sheet B', sheetB, expectedValuesB(sheetB), {numberOfRuns: 10}), - // () => benchmark('Sheet T', sheetT, expectedValuesT(sheetT), {numberOfRuns: 10}), - // () => benchmark('Column ranges', infiniteRanges, [{ - // address: 'AX50', - // value: 1.04519967355127e+63 - // }], {expectedTime: 1000, numberOfRuns: 10}) + () => benchmark('Sheet A', sheetA, expectedValuesA(sheetA), {numberOfRuns: 10}), + () => benchmark('Sheet B', sheetB, expectedValuesB(sheetB), {numberOfRuns: 10}), + () => benchmark('Sheet T', sheetT, expectedValuesT(sheetT), {numberOfRuns: 10}), + () => benchmark('Column ranges', infiniteRanges, [{ + address: 'AX50', + value: 1.04519967355127e+63 + }], {expectedTime: 1000, numberOfRuns: 10}) ) console.table(result.map(e => ({ From 3e1e6d2688e3a2e861803f2c3dd53951844b5dd8 Mon Sep 17 00:00:00 2001 From: izulin Date: Wed, 29 Jul 2020 15:53:27 +0200 Subject: [PATCH 67/97] fixed tests --- test/optional-parameters.spec.ts | 63 ++++++++++++++++---------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/test/optional-parameters.spec.ts b/test/optional-parameters.spec.ts index bc29db26e2..b2d0800d15 100644 --- a/test/optional-parameters.spec.ts +++ b/test/optional-parameters.spec.ts @@ -10,32 +10,19 @@ class FooPlugin extends FunctionPlugin { public static implementedFunctions = { 'FOO': { method: 'foo', + parameters: { + list: [ + { argumentType: 'string', defaultValue: 'default1'}, + { argumentType: 'string', defaultValue: 'default2'}, + ], + }, }, } public foo(ast: ProcedureAst, formulaAddress: SimpleCellAddress) { - if (ast.args.length > 2) { - return new CellError(ErrorType.NA) - } - - const arg1 = ast.args[0].type !== AstNodeType.EMPTY ? this.evaluateAst(ast.args[0], formulaAddress) : 'default1' - if (arg1 instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - const arg2 = ast.args[1].type !== AstNodeType.EMPTY ? this.evaluateAst(ast.args[1], formulaAddress) : 'default2' - if (arg2 instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - const coercedArg1 = coerceScalarToString(arg1) - if (coercedArg1 instanceof CellError) { - return coercedArg1 - } - const coercedArg2 = coerceScalarToString(arg2) - if (coercedArg2 instanceof CellError) { - return coercedArg2 - } - return coercedArg1 + '+' + coercedArg2 + return this.runFunction(ast.args, formulaAddress, this.parameters('FOO'), + (arg1, arg2) => arg1+'+'+arg2 + ) } } @@ -48,24 +35,38 @@ describe('Nonexistent parameters', () => { ['=foo( ,2)'], ['=foo(1,)'], ['=foo( , )'], + ['=foo(1)'], + ['=foo()'], ], {functionPlugins: [FooPlugin]}) expect(engine.getCellValue(adr('A1'))).toBe('1+2') - expect(engine.getCellValue(adr('A2'))).toBe('default1+2') - expect(engine.getCellValue(adr('A3'))).toBe('default1+2') - expect(engine.getCellValue(adr('A4'))).toBe('1+default2') - expect(engine.getCellValue(adr('A5'))).toBe('default1+default2') + expect(engine.getCellValue(adr('A2'))).toBe('+2') + expect(engine.getCellValue(adr('A3'))).toBe('+2') + expect(engine.getCellValue(adr('A4'))).toBe('1+') + expect(engine.getCellValue(adr('A5'))).toBe('+') + expect(engine.getCellValue(adr('A6'))).toBe('1+default2') + expect(engine.getCellValue(adr('A7'))).toBe('default1+default2') }) - it('typical function do not accept', () => { + it('log fails with coerce to 0', () => { const engine = HyperFormula.buildFromArray([ - ['=DATE(,1,1900)'], ['=LOG(10,)'], - ['=SUM(,1)'] ]) expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NUM)) - expect(engine.getCellValue(adr('A2'))).toEqual(detailedError(ErrorType.NUM)) - expect(engine.getCellValue(adr('A3'))).toEqual(detailedError(ErrorType.NUM)) }) + + it('other function coerce EmptyValue', () => { + const engine = HyperFormula.buildFromArray([ + ['=DATE(,1,1900)'], + ['=SUM(,1)'], + ['=CONCATENATE(,"abcd")'] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(1901) + expect(engine.getCellValue(adr('A2'))).toEqual(detailedError(ErrorType.NUM)) //TODO when SUM() is fixed, it should evaluate to 1 + expect(engine.getCellValue(adr('A3'))).toEqual('abcd') + }) + + }) From 25706ff2a5224b6e913e33fd06f76d579528af86 Mon Sep 17 00:00:00 2001 From: izulin Date: Wed, 29 Jul 2020 16:20:44 +0200 Subject: [PATCH 68/97] function text fixed --- src/interpreter/plugin/DatePlugin.ts | 26 +++----------------------- test/interpreter/function-text.spec.ts | 4 +++- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/src/interpreter/plugin/DatePlugin.ts b/src/interpreter/plugin/DatePlugin.ts index e37ac1bcb6..ef17248886 100644 --- a/src/interpreter/plugin/DatePlugin.ts +++ b/src/interpreter/plugin/DatePlugin.ts @@ -163,28 +163,8 @@ export class DatePlugin extends FunctionPlugin { * @param formulaAddress */ public text(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length !== 2) { - return new CellError(ErrorType.NA) - } - if (ast.args.some((ast) => ast.type === AstNodeType.EMPTY)) { - return new CellError(ErrorType.NUM) - } - - const dateArg = this.evaluateAst(ast.args[0], formulaAddress) - const formatArg = this.evaluateAst(ast.args[1], formulaAddress) - if (dateArg instanceof SimpleRangeValue) { - return new CellError(ErrorType.VALUE) - } - - const numberRepresentation = this.coerceScalarToNumberOrError(dateArg) - if (numberRepresentation instanceof CellError) { - return numberRepresentation - } - - if (typeof formatArg !== 'string') { - return new CellError(ErrorType.VALUE) - } - - return format(numberRepresentation, formatArg, this.config, this.interpreter.dateHelper) + return this.runFunction(ast.args, formulaAddress, this.parameters('TEXT'), + (numberRepresentation, formatArg) =>format(numberRepresentation, formatArg, this.config, this.interpreter.dateHelper) + ) } } diff --git a/test/interpreter/function-text.spec.ts b/test/interpreter/function-text.spec.ts index 7257ba2da5..fcf6ded58f 100644 --- a/test/interpreter/function-text.spec.ts +++ b/test/interpreter/function-text.spec.ts @@ -26,9 +26,11 @@ describe('Text', () => { it('wrong format argument', () => { const engine = HyperFormula.buildFromArray([ ['=TEXT(2, 42)'], + ['=TEXT(2, 0)'], ]) - expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.VALUE)) + expect(engine.getCellValue(adr('A1'))).toEqual("42") + expect(engine.getCellValue(adr('A2'))).toEqual("2") }) it('wrong date argument', () => { From b4d8fdf34e267a3f18083c418eebf254665630e9 Mon Sep 17 00:00:00 2001 From: izulin Date: Wed, 29 Jul 2020 16:24:03 +0200 Subject: [PATCH 69/97] linter --- test/interpreter/function-text.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/interpreter/function-text.spec.ts b/test/interpreter/function-text.spec.ts index fcf6ded58f..5385fb6826 100644 --- a/test/interpreter/function-text.spec.ts +++ b/test/interpreter/function-text.spec.ts @@ -29,8 +29,8 @@ describe('Text', () => { ['=TEXT(2, 0)'], ]) - expect(engine.getCellValue(adr('A1'))).toEqual("42") - expect(engine.getCellValue(adr('A2'))).toEqual("2") + expect(engine.getCellValue(adr('A1'))).toEqual('42') + expect(engine.getCellValue(adr('A2'))).toEqual('2') }) it('wrong date argument', () => { From dfb152fb050d5aa21f04ec6cc6bec0309519c1cc Mon Sep 17 00:00:00 2001 From: izulin Date: Wed, 29 Jul 2020 18:47:48 +0200 Subject: [PATCH 70/97] type unification --- src/Cell.ts | 5 ++-- src/CellValue.ts | 6 ++--- src/ColumnSearch/ColumnIndex.ts | 25 +++++++++---------- src/ColumnSearch/ColumnSearchStrategy.ts | 11 ++++---- src/ContentChanges.ts | 9 ++++--- .../AddressMapping/AddressMapping.ts | 5 ++-- src/DependencyGraph/DependencyGraph.ts | 5 ++-- src/DependencyGraph/FormulaCellVertex.ts | 11 ++++---- src/Evaluator.ts | 8 +++--- src/interpreter/ArithmeticHelper.ts | 6 ++--- src/interpreter/plugin/InformationPlugin.ts | 5 ++-- src/interpreter/plugin/VersionPlugin.ts | 4 +-- src/interpreter/plugin/VlookupPlugin.ts | 5 ++-- test/interpreter/function-version.spec.ts | 4 +-- 14 files changed, 55 insertions(+), 54 deletions(-) diff --git a/src/Cell.ts b/src/Cell.ts index 5340f66e96..190c9e132e 100644 --- a/src/Cell.ts +++ b/src/Cell.ts @@ -6,7 +6,7 @@ import {CellVertex, FormulaCellVertex, MatrixVertex, ParsingErrorVertex, ValueCellVertex} from './DependencyGraph' import {CellAddress} from './parser' import {AddressWithSheet} from './parser/Address' -import {SimpleRangeValue} from './interpreter/InterpreterValue' +import {InterpreterValue, SimpleRangeValue} from './interpreter/InterpreterValue' /** * Possible errors returned by our interpreter. @@ -35,7 +35,6 @@ export const EmptyValue = Symbol('Empty value') export type EmptyValueType = typeof EmptyValue export type InternalNoErrorCellValue = number | string | boolean | EmptyValueType export type InternalScalarValue = InternalNoErrorCellValue | CellError -export type InternalCellValue = InternalScalarValue | SimpleRangeValue export enum CellType { FORMULA = 'FORMULA', @@ -82,7 +81,7 @@ export const CellValueTypeOrd = (arg: CellValueType): number => { } } -export const getCellValueType = (cellValue: InternalCellValue): CellValueType => { +export const getCellValueType = (cellValue: InterpreterValue): CellValueType => { if (cellValue === EmptyValue) { return CellValueType.EMPTY } diff --git a/src/CellValue.ts b/src/CellValue.ts index b0c1e5cabc..00cdb2c858 100644 --- a/src/CellValue.ts +++ b/src/CellValue.ts @@ -3,11 +3,11 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {CellError, EmptyValue, ErrorType, InternalCellValue, simpleCellAddress, SimpleCellAddress} from './Cell' +import {CellError, EmptyValue, ErrorType, simpleCellAddress, SimpleCellAddress} from './Cell' import {Config} from './Config' import {CellValueChange} from './ContentChanges' import {NamedExpressions} from './NamedExpressions' -import {SimpleRangeValue} from './interpreter/InterpreterValue' +import {InterpreterValue, SimpleRangeValue} from './interpreter/InterpreterValue' export type NoErrorCellValue = number | string | boolean | null export type CellValue = NoErrorCellValue | DetailedCellError @@ -87,7 +87,7 @@ export class Exporter { } } - public exportValue(value: InternalCellValue): CellValue { + public exportValue(value: InterpreterValue): CellValue { if (value instanceof SimpleRangeValue) { return this.detailedError(new CellError(ErrorType.VALUE)) } else if (this.config.smartRounding && typeof value == 'number') { diff --git a/src/ColumnSearch/ColumnIndex.ts b/src/ColumnSearch/ColumnIndex.ts index 6c8aa349ab..af2dd3dad9 100644 --- a/src/ColumnSearch/ColumnIndex.ts +++ b/src/ColumnSearch/ColumnIndex.ts @@ -6,7 +6,6 @@ import {AbsoluteCellRange} from '../AbsoluteCellRange' import { CellError, - InternalCellValue, InternalNoErrorCellValue, InternalScalarValue, movedSimpleCellAddress, @@ -23,9 +22,9 @@ import {ColumnSearchStrategy} from './ColumnSearchStrategy' import {AddRowsTransformer} from '../dependencyTransformers/AddRowsTransformer' import {RemoveRowsTransformer} from '../dependencyTransformers/RemoveRowsTransformer' import {FormulaTransformer} from '../dependencyTransformers/Transformer' -import {SimpleRangeValue} from '../interpreter/InterpreterValue' +import {InterpreterValue, SimpleRangeValue} from '../interpreter/InterpreterValue' -type ColumnMap = Map +type ColumnMap = Map interface ValueIndex { version: number, @@ -49,7 +48,7 @@ export class ColumnIndex implements ColumnSearchStrategy { this.binarySearchStrategy = new ColumnBinarySearch(dependencyGraph, config) } - public add(value: InternalCellValue | Matrix, address: SimpleCellAddress) { + public add(value: InterpreterValue | Matrix, address: SimpleCellAddress) { if (value instanceof Matrix) { for (const [matrixValue, cellAddress] of value.generateValues(address)) { this.addSingleCellValue(matrixValue, cellAddress) @@ -59,7 +58,7 @@ export class ColumnIndex implements ColumnSearchStrategy { } } - public remove(value: InternalCellValue | Matrix | null, address: SimpleCellAddress) { + public remove(value: InterpreterValue | Matrix | null, address: SimpleCellAddress) { if (!value) { return } @@ -73,7 +72,7 @@ export class ColumnIndex implements ColumnSearchStrategy { } } - public change(oldValue: InternalCellValue | Matrix | null, newValue: InternalScalarValue | Matrix, address: SimpleCellAddress) { + public change(oldValue: InterpreterValue | Matrix | null, newValue: InternalScalarValue | Matrix, address: SimpleCellAddress) { if (oldValue === newValue) { return } @@ -113,7 +112,7 @@ export class ColumnIndex implements ColumnSearchStrategy { return rowNumber <= range.end.row ? rowNumber : this.binarySearchStrategy.find(key, range, sorted) } - public advancedFind(keyMatcher: (arg: InternalCellValue) => boolean, range: AbsoluteCellRange): number { + public advancedFind(keyMatcher: (arg: InterpreterValue) => boolean, range: AbsoluteCellRange): number { return this.binarySearchStrategy.advancedFind(keyMatcher, range) } @@ -154,7 +153,7 @@ export class ColumnIndex implements ColumnSearchStrategy { return columnMap } - public getValueIndex(sheet: number, col: number, value: InternalCellValue): ValueIndex { + public getValueIndex(sheet: number, col: number, value: InterpreterValue): ValueIndex { const columnMap = this.getColumnMap(sheet, col) let index = this.getColumnMap(sheet, col).get(value) if (!index) { @@ -167,7 +166,7 @@ export class ColumnIndex implements ColumnSearchStrategy { return index } - public ensureRecentData(sheet: number, col: number, value: InternalCellValue) { + public ensureRecentData(sheet: number, col: number, value: InterpreterValue) { const valueIndex = this.getValueIndex(sheet, col, value) const actualVersion = this.transformingService.version() if (valueIndex.version === actualVersion) { @@ -190,7 +189,7 @@ export class ColumnIndex implements ColumnSearchStrategy { this.index.clear() } - private addSingleCellValue(value: InternalCellValue, address: SimpleCellAddress) { + private addSingleCellValue(value: InterpreterValue, address: SimpleCellAddress) { this.stats.measure(StatType.BUILD_COLUMN_INDEX, () => { this.ensureRecentData(address.sheet, address.col, value) const valueIndex = this.getValueIndex(address.sheet, address.col, value) @@ -198,7 +197,7 @@ export class ColumnIndex implements ColumnSearchStrategy { }) } - private removeSingleValue(value: InternalCellValue, address: SimpleCellAddress) { + private removeSingleValue(value: InterpreterValue, address: SimpleCellAddress) { this.stats.measure(StatType.BUILD_COLUMN_INDEX, () => { this.ensureRecentData(address.sheet, address.col, value) @@ -221,12 +220,12 @@ export class ColumnIndex implements ColumnSearchStrategy { }) } - private addRows(col: number, rowsSpan: RowsSpan, value: InternalCellValue) { + private addRows(col: number, rowsSpan: RowsSpan, value: InterpreterValue) { const valueIndex = this.getValueIndex(rowsSpan.sheet, col, value) this.shiftRows(valueIndex, rowsSpan.rowStart, rowsSpan.numberOfRows) } - private removeRows(col: number, rowsSpan: RowsSpan, value: InternalCellValue) { + private removeRows(col: number, rowsSpan: RowsSpan, value: InterpreterValue) { const valueIndex = this.getValueIndex(rowsSpan.sheet, col, value) this.removeRowsFromValues(valueIndex, rowsSpan) this.shiftRows(valueIndex, rowsSpan.rowEnd + 1, -rowsSpan.numberOfRows) diff --git a/src/ColumnSearch/ColumnSearchStrategy.ts b/src/ColumnSearch/ColumnSearchStrategy.ts index 0450f0f963..8ba663889b 100644 --- a/src/ColumnSearch/ColumnSearchStrategy.ts +++ b/src/ColumnSearch/ColumnSearchStrategy.ts @@ -4,9 +4,10 @@ */ import {AbsoluteCellRange} from '../AbsoluteCellRange' -import {InternalCellValue, InternalNoErrorCellValue, InternalScalarValue, SimpleCellAddress} from '../Cell' +import {InternalNoErrorCellValue, InternalScalarValue, SimpleCellAddress} from '../Cell' import {Config} from '../Config' import {DependencyGraph} from '../DependencyGraph' +import {InterpreterValue} from '../interpreter/InterpreterValue' import {Matrix} from '../Matrix' import {Statistics} from '../statistics/Statistics' import {ColumnBinarySearch} from './ColumnBinarySearch' @@ -14,11 +15,11 @@ import {ColumnIndex} from './ColumnIndex' import {ColumnsSpan} from '../Span' export interface ColumnSearchStrategy { - add(value: InternalCellValue | Matrix, address: SimpleCellAddress): void, + add(value: InterpreterValue | Matrix, address: SimpleCellAddress): void, - remove(value: InternalCellValue | Matrix | null, address: SimpleCellAddress): void, + remove(value: InterpreterValue | Matrix | null, address: SimpleCellAddress): void, - change(oldValue: InternalCellValue | Matrix | null, newValue: InternalCellValue | Matrix, address: SimpleCellAddress): void, + change(oldValue: InterpreterValue | Matrix | null, newValue: InterpreterValue | Matrix, address: SimpleCellAddress): void, addColumns(columnsSpan: ColumnsSpan): void, @@ -32,7 +33,7 @@ export interface ColumnSearchStrategy { find(key: InternalNoErrorCellValue, range: AbsoluteCellRange, sorted: boolean): number, - advancedFind(keyMatcher: (arg: InternalCellValue) => boolean, range: AbsoluteCellRange): number, + advancedFind(keyMatcher: (arg: InterpreterValue) => boolean, range: AbsoluteCellRange): number, destroy(): void, } diff --git a/src/ContentChanges.ts b/src/ContentChanges.ts index 3d5e289827..db0f5d3d92 100644 --- a/src/ContentChanges.ts +++ b/src/ContentChanges.ts @@ -3,14 +3,15 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {InternalCellValue, SimpleCellAddress} from './Cell' +import {SimpleCellAddress} from './Cell' +import {InterpreterValue} from './interpreter/InterpreterValue' import {Matrix} from './Matrix' export interface CellValueChange { sheet: number, row: number, col: number, - value: InternalCellValue, + value: InterpreterValue, } export interface ChangeExporter { @@ -37,7 +38,7 @@ export class ContentChanges { } } - public addChange(newValue: InternalCellValue, address: SimpleCellAddress): void { + public addChange(newValue: InterpreterValue, address: SimpleCellAddress): void { this.addSingleCellValue(newValue, address) } @@ -61,7 +62,7 @@ export class ContentChanges { return this.changes === [] } - private addSingleCellValue(value: InternalCellValue, address: SimpleCellAddress) { + private addSingleCellValue(value: InterpreterValue, address: SimpleCellAddress) { this.add({ sheet: address.sheet, col: address.col, diff --git a/src/DependencyGraph/AddressMapping/AddressMapping.ts b/src/DependencyGraph/AddressMapping/AddressMapping.ts index d938e48287..c25c88a1d3 100644 --- a/src/DependencyGraph/AddressMapping/AddressMapping.ts +++ b/src/DependencyGraph/AddressMapping/AddressMapping.ts @@ -3,7 +3,8 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {EmptyValue, InternalCellValue, SimpleCellAddress} from '../../Cell' +import {EmptyValue, SimpleCellAddress} from '../../Cell' +import {InterpreterValue} from '../../interpreter/InterpreterValue' import {ColumnsSpan, RowsSpan} from '../../Span' import {MatrixVertex} from '../index' import {CellVertex} from '../Vertex' @@ -64,7 +65,7 @@ export class AddressMapping { this.addSheet(sheetId, new strategyConstructor(width, height)) } - public getCellValue(address: SimpleCellAddress): InternalCellValue { + public getCellValue(address: SimpleCellAddress): InterpreterValue { const vertex = this.getCell(address) if (vertex === null) { diff --git a/src/DependencyGraph/DependencyGraph.ts b/src/DependencyGraph/DependencyGraph.ts index 0b2885deb9..c556644e2b 100644 --- a/src/DependencyGraph/DependencyGraph.ts +++ b/src/DependencyGraph/DependencyGraph.ts @@ -10,7 +10,6 @@ import { CellError, EmptyValue, ErrorType, - InternalCellValue, InternalScalarValue, simpleCellAddress, SimpleCellAddress @@ -42,7 +41,7 @@ import {RangeMapping} from './RangeMapping' import {SheetMapping} from './SheetMapping' import {ValueCellVertexValue} from './ValueCellVertex' import {FunctionRegistry} from '../interpreter/FunctionRegistry' -import {SimpleRangeValue} from '../interpreter/InterpreterValue' +import {InterpreterValue, SimpleRangeValue} from '../interpreter/InterpreterValue' export class DependencyGraph { /* @@ -597,7 +596,7 @@ export class DependencyGraph { return this.addressMapping.getCell(address) } - public getCellValue(address: SimpleCellAddress): InternalCellValue { + public getCellValue(address: SimpleCellAddress): InterpreterValue { return this.addressMapping.getCellValue(address) } diff --git a/src/DependencyGraph/FormulaCellVertex.ts b/src/DependencyGraph/FormulaCellVertex.ts index 8af673cd72..45979e2d65 100644 --- a/src/DependencyGraph/FormulaCellVertex.ts +++ b/src/DependencyGraph/FormulaCellVertex.ts @@ -3,7 +3,8 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {InternalCellValue, SimpleCellAddress} from '../Cell' +import {SimpleCellAddress} from '../Cell' +import {InterpreterValue} from '../interpreter/InterpreterValue' import {LazilyTransformingAstService} from '../LazilyTransformingAstService' import {Ast} from '../parser' @@ -12,7 +13,7 @@ import {Ast} from '../parser' */ export class FormulaCellVertex { /** Most recently computed value of this formula. */ - private cachedCellValue: InternalCellValue | null + private cachedCellValue: InterpreterValue | null constructor( /** Formula in AST format */ @@ -60,14 +61,14 @@ export class FormulaCellVertex { /** * Sets computed cell value stored in this vertex */ - public setCellValue(cellValue: InternalCellValue) { + public setCellValue(cellValue: InterpreterValue) { this.cachedCellValue = cellValue } /** * Returns cell value stored in vertex */ - public getCellValue(): InternalCellValue { + public getCellValue(): InterpreterValue { if (this.cachedCellValue !== null) { return this.cachedCellValue } else { @@ -75,7 +76,7 @@ export class FormulaCellVertex { } } - public valueOrNull(): InternalCellValue | null { + public valueOrNull(): InterpreterValue | null { return this.cachedCellValue } diff --git a/src/Evaluator.ts b/src/Evaluator.ts index 9e1e5079d0..8011a8a401 100644 --- a/src/Evaluator.ts +++ b/src/Evaluator.ts @@ -5,7 +5,7 @@ import {AbsoluteCellRange} from './AbsoluteCellRange' import {absolutizeDependencies} from './absolutizeDependencies' -import {CellError, ErrorType, InternalCellValue, SimpleCellAddress} from './Cell' +import {CellError, ErrorType, SimpleCellAddress} from './Cell' import {ColumnSearchStrategy} from './ColumnSearch/ColumnSearchStrategy' import {Config} from './Config' import {ContentChanges} from './ContentChanges' @@ -13,7 +13,7 @@ import {DateTimeHelper} from './DateTimeHelper' import {DependencyGraph, FormulaCellVertex, MatrixVertex, RangeVertex, Vertex} from './DependencyGraph' import {fixNegativeZero, isNumberOverflow} from './interpreter/ArithmeticHelper' import {Interpreter} from './interpreter/Interpreter' -import {SimpleRangeValue} from './interpreter/InterpreterValue' +import {InterpreterValue, SimpleRangeValue} from './interpreter/InterpreterValue' import {Matrix} from './Matrix' import {Ast, RelativeDependency} from './parser' import {Statistics, StatType} from './statistics' @@ -110,7 +110,7 @@ export class Evaluator { this.interpreter.destroy() } - public runAndForget(ast: Ast, address: SimpleCellAddress, dependencies: RelativeDependency[]): InternalCellValue { + public runAndForget(ast: Ast, address: SimpleCellAddress, dependencies: RelativeDependency[]): InterpreterValue { const tmpRanges: RangeVertex[] = [] for (const dep of absolutizeDependencies(dependencies, address)) { if (dep instanceof AbsoluteCellRange) { @@ -165,7 +165,7 @@ export class Evaluator { }) } - private evaluateAstToCellValue(ast: Ast, formulaAddress: SimpleCellAddress): InternalCellValue { + private evaluateAstToCellValue(ast: Ast, formulaAddress: SimpleCellAddress): InterpreterValue { const interpreterValue = this.interpreter.evaluateAst(ast, formulaAddress) if (interpreterValue instanceof SimpleRangeValue) { return interpreterValue diff --git a/src/interpreter/ArithmeticHelper.ts b/src/interpreter/ArithmeticHelper.ts index 08fb88e222..c80b4fe649 100644 --- a/src/interpreter/ArithmeticHelper.ts +++ b/src/interpreter/ArithmeticHelper.ts @@ -8,7 +8,7 @@ import { CellValueTypeOrd, EmptyValue, ErrorType, - getCellValueType, InternalCellValue, + getCellValueType, InternalNoErrorCellValue, InternalScalarValue } from '../Cell' @@ -32,12 +32,12 @@ export class ArithmeticHelper { this.actualEps = config.smartRounding ? config.precisionEpsilon : 0 } - public eqMatcherFunction(pattern: string): (arg: InternalCellValue) => boolean { + public eqMatcherFunction(pattern: string): (arg: InterpreterValue) => boolean { const regexp = this.buildRegex(pattern) return (cellValue) => (typeof cellValue === 'string' && regexp.test(this.normalizeString(cellValue))) } - public neqMatcherFunction(pattern: string): (arg: InternalCellValue) => boolean { + public neqMatcherFunction(pattern: string): (arg: InterpreterValue) => boolean { const regexp = this.buildRegex(pattern) return (cellValue) => { return (typeof cellValue !== 'string' || !regexp.test(this.normalizeString(cellValue))) diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index 390e0694fd..ff5f4a1049 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -4,8 +4,9 @@ */ import {AbsoluteCellRange} from '../../AbsoluteCellRange' -import {CellError, EmptyValue, ErrorType, InternalCellValue, InternalScalarValue, SimpleCellAddress} from '../../Cell' +import {CellError, EmptyValue, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {AstNodeType, ProcedureAst} from '../../parser' +import {InterpreterValue} from '../InterpreterValue' import {FunctionPlugin} from './FunctionPlugin' /** @@ -195,7 +196,7 @@ export class InformationPlugin extends FunctionPlugin { } } - public index(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalCellValue { + public index(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InterpreterValue { const rangeArg = ast.args[0] if (ast.args.length < 1 || ast.args.length > 3) { return new CellError(ErrorType.NA) diff --git a/src/interpreter/plugin/VersionPlugin.ts b/src/interpreter/plugin/VersionPlugin.ts index e0a829104c..48dc882d91 100644 --- a/src/interpreter/plugin/VersionPlugin.ts +++ b/src/interpreter/plugin/VersionPlugin.ts @@ -3,7 +3,7 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import { InternalCellValue } from '../../Cell' +import {InterpreterValue} from '../InterpreterValue' import { FunctionPlugin } from './FunctionPlugin' import { HyperFormula } from '../../HyperFormula' import { LicenseKeyValidityState } from '../../helpers/licenseKeyValidator' @@ -23,7 +23,7 @@ export class VersionPlugin extends FunctionPlugin { }, } - public version(): InternalCellValue { + public version(): InterpreterValue { const { licenseKeyValidityState: validityState, licenseKey, diff --git a/src/interpreter/plugin/VlookupPlugin.ts b/src/interpreter/plugin/VlookupPlugin.ts index 74372d5e37..b25be12985 100644 --- a/src/interpreter/plugin/VlookupPlugin.ts +++ b/src/interpreter/plugin/VlookupPlugin.ts @@ -7,7 +7,6 @@ import {AbsoluteCellRange} from '../../AbsoluteCellRange' import { CellError, ErrorType, - InternalCellValue, InternalScalarValue, simpleCellAddress, SimpleCellAddress @@ -33,7 +32,7 @@ export class VlookupPlugin extends FunctionPlugin { * @param ast * @param formulaAddress */ - public vlookup(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalCellValue { + public vlookup(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InterpreterValue { if (ast.args.length < 3 || ast.args.length > 4) { return new CellError(ErrorType.NA) } @@ -130,7 +129,7 @@ export class VlookupPlugin extends FunctionPlugin { } } - private doVlookup(key: any, range: AbsoluteCellRange, index: number, sorted: boolean): InternalCellValue { + private doVlookup(key: any, range: AbsoluteCellRange, index: number, sorted: boolean): InterpreterValue { this.dependencyGraph.stats.start(StatType.VLOOKUP) const searchedRange = AbsoluteCellRange.spanFrom(range.start, 1, range.height()) diff --git a/test/interpreter/function-version.spec.ts b/test/interpreter/function-version.spec.ts index 1403051f6c..52b85e1789 100644 --- a/test/interpreter/function-version.spec.ts +++ b/test/interpreter/function-version.spec.ts @@ -1,5 +1,5 @@ +import {InterpreterValue} from '../../src/interpreter/InterpreterValue' import { FunctionPlugin } from '../../src/interpreter/plugin/FunctionPlugin' -import { InternalCellValue } from '../../src/Cell' import { HyperFormula } from '../../src' import { adr } from '../testUtils' import {ProtectedFunctionError} from '../../src/errors' @@ -75,7 +75,7 @@ describe('Function VERSION', () => { } } - public version(): InternalCellValue { + public version(): InterpreterValue { return 'version' } } From 4ddac2dbd59deaceeef721e1c5c78e1cde7fd8fd Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Fri, 24 Jul 2020 12:47:33 +0200 Subject: [PATCH 71/97] Function ISERR --- docs/guide/built-in-functions.md | 3 +- src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + src/interpreter/plugin/InformationPlugin.ts | 73 +++++++++++++++------ test/interpreter/function-iserr.spec.ts | 52 +++++++++++++++ 19 files changed, 124 insertions(+), 20 deletions(-) create mode 100644 test/interpreter/function-iserr.spec.ts diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index ae88cded97..54bebc414f 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -82,7 +82,8 @@ lets you design your own [custom functions](custom-functions). | ERF | Engineering | Returns values of the Gaussian error integral. | ERF(Lower_Limit; Upper_Limit) | | ERFC | Engineering | Returns complementary values of the Gaussian error integral between x and infinity. | ERFC(Lower_Limit) | | ISBLANK | Information | Returns TRUE if the reference to a cell is blank. | ISBLANK(Value) | -| ISERROR | Information | The ISERROR tests if the cells contain general error values. | ISERROR(Value) | +| ISERR | Information | Returns TRUE if the value is error value except #N/A!. | ISERR(Value) | +| ISERROR | Information | Returns TRUE if the value is general error value. | ISERROR(Value) | | ISEVEN | Information | Returns TRUE if the value is an even integer, or FALSE if the value is odd. | ISEVEN(Value) | | ISLOGICAL | Information | Tests for a logical value (TRUE or FALSE). | ISLOGICAL(Value) | | ISNONTEXT | Information | Tests if the cell contents are text or numbers, and returns FALSE if the contents are text. | ISNONTEXT(Value) | diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index d7e7192635..528ad20544 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -78,6 +78,7 @@ const dictionary: RawTranslationPackage = { INT: 'CELÁ.ČÁST', IPMT: 'PLATBA.ÚROK', ISBLANK: 'JE.PRÁZDNÉ', + ISERR: 'JE.CHYBA', ISERROR: 'JE.CHYBHODN', ISEVEN: 'ISEVEN', ISLOGICAL: 'JE.LOGHODN', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 81e54b65fa..e27ae3481b 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -78,6 +78,7 @@ const dictionary: RawTranslationPackage = { INT: 'HELTAL', IPMT: 'R.YDELSE', ISBLANK: 'ER.TOM', + ISERR: 'ER.FJL', ISERROR: 'ER.FEJL', ISEVEN: 'ER.LIGE', ISLOGICAL: 'ER.LOGISK', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index 119d75150a..004eee80f1 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -78,6 +78,7 @@ const dictionary: RawTranslationPackage = { INT: 'GANZZAHL', IPMT: 'ZINSZ', ISBLANK: 'ISTLEER', + ISERR: 'ISTFEHL', ISERROR: 'ISTFEHLER', ISEVEN: 'ISTGERADE', ISLOGICAL: 'ISTLOG', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index 679b95dd4d..c3612e37f0 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -78,6 +78,7 @@ const dictionary: RawTranslationPackage = { INT: 'INT', IPMT: 'IPMT', ISBLANK: 'ISBLANK', + ISERR: 'ISERR', ISERROR: 'ISERROR', ISEVEN: 'ISEVEN', ISLOGICAL: 'ISLOGICAL', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index e553ee2ad1..dcd0933350 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -78,6 +78,7 @@ export const dictionary: RawTranslationPackage = { INT: 'ENTERO', IPMT: 'PAGOINT', ISBLANK: 'ESBLANCO', + ISERR: 'ESERR', ISERROR: 'ESERROR', ISEVEN: 'ES.PAR', ISLOGICAL: 'ESLOGICO', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 2d1afb7f55..9ffdc12d6d 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -78,6 +78,7 @@ const dictionary: RawTranslationPackage = { INT: 'KOKONAISLUKU', IPMT: 'IPMT', ISBLANK: 'ONTYHJÄ', + ISERR: 'ONVIRH', ISERROR: 'ONVIRHE', ISEVEN: 'ONPARILLINEN', ISLOGICAL: 'ONTOTUUS', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index 7cac6074e3..a51f89ff49 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -78,6 +78,7 @@ const dictionary: RawTranslationPackage = { INT: 'ENT', IPMT: 'INTPER', ISBLANK: 'ESTVIDE', + ISERR: 'ESTERR', ISERROR: 'ESTERREUR', ISEVEN: 'EST.PAIR', ISLOGICAL: 'ESTLOGIQUE', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index 2e5b30ec7e..116d791e25 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -78,6 +78,7 @@ const dictionary: RawTranslationPackage = { INT: 'INT', IPMT: 'RRÉSZLET', ISBLANK: 'ÜRES', + ISERR: 'HIBA.E', ISERROR: 'HIBÁS', ISEVEN: 'PÁROSE', ISLOGICAL: 'LOGIKAI', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index da3dca2863..4a29d165b0 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -78,6 +78,7 @@ const dictionary: RawTranslationPackage = { INT: 'INT', IPMT: 'INTERESSI', ISBLANK: 'VAL.VUOTO', + ISERR: 'VAL.ERR', ISERROR: 'VAL.ERRORE', ISEVEN: 'VAL.PARI', ISLOGICAL: 'VAL.LOGICO', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index 265bc3f1d6..5ccc7dd291 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -78,6 +78,7 @@ const dictionary: RawTranslationPackage = { INT: 'HELTALL', IPMT: 'RAVDRAG', ISBLANK: 'ERTOM', + ISERR: 'ERF', ISERROR: 'ERFEIL', ISEVEN: 'ERPARTALL', ISLOGICAL: 'ERLOGISK', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index cd9d5f6652..a3739f2d80 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -78,6 +78,7 @@ const dictionary: RawTranslationPackage = { INT: 'INTEGER', IPMT: 'IBET', ISBLANK: 'ISLEEG', + ISERR: 'ISFOUT2', ISERROR: 'ISFOUT', ISEVEN: 'IS.EVEN', ISLOGICAL: 'ISLOGISCH', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index 107168e1b0..df5c5af512 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -78,6 +78,7 @@ const dictionary: RawTranslationPackage = { INT: 'ZAOKR.DO.CAŁK', IPMT: 'IPMT', ISBLANK: 'CZY.PUSTA', + ISERR: 'CZY.BŁ', ISERROR: 'CZY.BŁĄD', ISEVEN: 'CZY.PARZYSTE', ISLOGICAL: 'CZY.LOGICZNA', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index cfa0ca781f..b961e9ac4d 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -78,6 +78,7 @@ const dictionary: RawTranslationPackage = { INT: 'INT', IPMT: 'IPGTO', ISBLANK: 'ÉCÉL.VAZIA', + ISERR: 'ÉERRO', ISERROR: 'ÉERROS', ISEVEN: 'ÉPAR', ISLOGICAL: 'ÉLÓGICO', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index 41a3b317ec..2938048919 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -78,6 +78,7 @@ const dictionary: RawTranslationPackage = { INT: 'ЦЕЛОЕ', IPMT: 'ПРПЛТ', ISBLANK: 'ЕПУСТО', + ISERR: 'ЕОШ', ISERROR: 'ЕОШИБКА', ISEVEN: 'ЕЧЁТН', ISLOGICAL: 'ЕЛОГИЧ', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index ea1b4f1e17..04007f77ba 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -78,6 +78,7 @@ const dictionary: RawTranslationPackage = { INT: 'HELTAL', IPMT: 'RBETALNING', ISBLANK: 'ÄRTOM', + ISERR: 'ÄRF', ISERROR: 'ÄRFEL', ISEVEN: 'ÄRJÄMN', ISLOGICAL: 'ÄRLOGISK', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index edf855d702..a85505437d 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -78,6 +78,7 @@ const dictionary: RawTranslationPackage = { INT: 'TAMSAYI', IPMT: 'FAİZTUTARI', ISBLANK: 'EBOŞSA', + ISERR: 'EHATA', ISERROR: 'EHATALIYSA', ISEVEN: 'ÇİFTMİ', ISLOGICAL: 'EMANTIKSALSA', diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index ff5f4a1049..de5a6e9972 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -14,41 +14,61 @@ import {FunctionPlugin} from './FunctionPlugin' */ export class InformationPlugin extends FunctionPlugin { public static implementedFunctions = { + 'ISERR': { + method: 'iserr', + parameters: { + list: [ + {argumentType: 'scalar'} + ] + } + }, 'ISERROR': { method: 'iserror', - parameters: { list: [ - { argumentType: 'scalar'} - ]} + parameters: { + list: [ + {argumentType: 'scalar'} + ] + } }, 'ISBLANK': { method: 'isblank', - parameters: { list: [ - { argumentType: 'scalar'} - ]} + parameters: { + list: [ + {argumentType: 'scalar'} + ] + } }, 'ISNUMBER': { method: 'isnumber', - parameters: { list: [ - { argumentType: 'scalar'} - ]} + parameters: { + list: [ + {argumentType: 'scalar'} + ] + } }, 'ISLOGICAL': { method: 'islogical', - parameters: { list: [ - { argumentType: 'scalar'} - ]} + parameters: { + list: [ + {argumentType: 'scalar'} + ] + } }, 'ISTEXT': { method: 'istext', - parameters: { list: [ - { argumentType: 'scalar'} - ]} + parameters: { + list: [ + {argumentType: 'scalar'} + ] + } }, 'ISNONTEXT': { method: 'isnontext', - parameters: { list: [ - { argumentType: 'scalar'} - ]} + parameters: { + list: [ + {argumentType: 'scalar'} + ] + } }, 'COLUMNS': { method: 'columns', @@ -62,7 +82,21 @@ export class InformationPlugin extends FunctionPlugin { }, 'INDEX': { method: 'index', - }, + } + } + + /** + * Corresponds to ISERR(value) + * + * Returns true if provided value is an error except #N/A! + * + * @param ast + * @param formulaAddress + */ + public iserr(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunction(ast.args, formulaAddress, this.parameters('ISERR'), (arg: InternalScalarValue) => + (arg instanceof CellError && arg.type !== ErrorType.NA) + ) } /** @@ -148,6 +182,7 @@ export class InformationPlugin extends FunctionPlugin { (typeof arg !== 'string') ) } + /** * Corresponds to COLUMNS(range) * diff --git a/test/interpreter/function-iserr.spec.ts b/test/interpreter/function-iserr.spec.ts new file mode 100644 index 0000000000..b678e8eec4 --- /dev/null +++ b/test/interpreter/function-iserr.spec.ts @@ -0,0 +1,52 @@ +import {HyperFormula} from '../../src' +import {ErrorType} from '../../src/Cell' +import {adr, detailedError} from '../testUtils' + +describe('Function ISERR', () => { + it('should return true for common errors', () => { + const engine = HyperFormula.buildFromArray([ + ['=ISERR(1/0)', '=ISERR(FOO())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(true) + expect(engine.getCellValue(adr('B1'))).toEqual(true) + }) + + it('should return false for #N/A!', () => { + const engine = HyperFormula.buildFromArray([ + ['=ISERR(TRUE(1))'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(false) + }) + + it('should return false for valid formulas', () => { + const engine = HyperFormula.buildFromArray([ + ['=ISERR(1)', '=ISERR(TRUE())', '=ISERR("foo")', '=ISERR(ISERR(1/0))', '=ISERR(A1)'], + ]) + expect(engine.getCellValue(adr('A1'))).toEqual(false) + expect(engine.getCellValue(adr('B1'))).toEqual(false) + expect(engine.getCellValue(adr('C1'))).toEqual(false) + expect(engine.getCellValue(adr('D1'))).toEqual(false) + expect(engine.getCellValue(adr('E1'))).toEqual(false) + }) + + it('takes exactly one argument', () => { + const engine = HyperFormula.buildFromArray([ + ['=ISERR(1, 2)', '=ISERR()'], + ]) + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NA)) + expect(engine.getCellValue(adr('B1'))).toEqual(detailedError(ErrorType.NA)) + }) + + // Inconsistency with Product 1 + it('range value results in VALUE error', () => { + const engine = HyperFormula.buildFromArray([ + ['=4/1'], + ['=4/0', '=ISERR(A1:A3)'], + ['=4/2'], + ]) + + expect(engine.getCellValue(adr('B2'))).toEqual(detailedError(ErrorType.VALUE)) + }) +}) From 5e8a3c9a774f90db5693180109e4f503b2921fb5 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Fri, 24 Jul 2020 12:56:25 +0200 Subject: [PATCH 72/97] Refactor isEven/isOdd functions --- src/interpreter/plugin/IsEvenPlugin.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/interpreter/plugin/IsEvenPlugin.ts b/src/interpreter/plugin/IsEvenPlugin.ts index f8154d7b4e..554764753e 100644 --- a/src/interpreter/plugin/IsEvenPlugin.ts +++ b/src/interpreter/plugin/IsEvenPlugin.ts @@ -11,9 +11,11 @@ export class IsEvenPlugin extends FunctionPlugin { public static implementedFunctions = { 'ISEVEN': { method: 'iseven', - parameters: { list: [ - { argumentType: 'number'} - ]} + parameters: { + list: [ + {argumentType: 'number'} + ] + } }, } From 88109d63bf9d9ffdfc7248093a0d74d1d27de2a8 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Fri, 24 Jul 2020 13:12:52 +0200 Subject: [PATCH 73/97] Function ISNA --- docs/guide/built-in-functions.md | 1 + src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + src/interpreter/plugin/InformationPlugin.ts | 20 ++++++++++ test/interpreter/function-isna.spec.ts | 41 +++++++++++++++++++++ 19 files changed, 78 insertions(+) create mode 100644 test/interpreter/function-isna.spec.ts diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index 54bebc414f..99a2384c65 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -86,6 +86,7 @@ lets you design your own [custom functions](custom-functions). | ISERROR | Information | Returns TRUE if the value is general error value. | ISERROR(Value) | | ISEVEN | Information | Returns TRUE if the value is an even integer, or FALSE if the value is odd. | ISEVEN(Value) | | ISLOGICAL | Information | Tests for a logical value (TRUE or FALSE). | ISLOGICAL(Value) | +| ISNA | Information | Returns TRUE if the value is #N/A! error. | ISNA(Value) | | ISNONTEXT | Information | Tests if the cell contents are text or numbers, and returns FALSE if the contents are text. | ISNONTEXT(Value) | | ISNUMBER | Information | Returns TRUE if the value refers to a number. | ISNUMBER(Value) | | ISODD | Information | Returns TRUE if the value is odd, or FALSE if the number is even. | ISODD(Value) | diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index 528ad20544..e533deea9d 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERROR: 'JE.CHYBHODN', ISEVEN: 'ISEVEN', ISLOGICAL: 'JE.LOGHODN', + ISNA: 'JE.NEDEF', ISNONTEXT: 'JE.NETEXT', ISNUMBER: 'JE.ČISLO', ISODD: 'ISODD', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index e27ae3481b..35a449c450 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERROR: 'ER.FEJL', ISEVEN: 'ER.LIGE', ISLOGICAL: 'ER.LOGISK', + ISNA: 'ER.IKKE.TILGÆNGELIG', ISNONTEXT: 'ER.IKKE.TEKST', ISNUMBER: 'ER.TAL', ISODD: 'ER.ULIGE', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index 004eee80f1..fb0f483737 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERROR: 'ISTFEHLER', ISEVEN: 'ISTGERADE', ISLOGICAL: 'ISTLOG', + ISNA: 'ISTNV', ISNONTEXT: 'ISTKTEXT', ISNUMBER: 'ISTZAHL', ISODD: 'ISTUNGERADE', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index c3612e37f0..ff780f3da2 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERROR: 'ISERROR', ISEVEN: 'ISEVEN', ISLOGICAL: 'ISLOGICAL', + ISNA: 'ISNA', ISNONTEXT: 'ISNONTEXT', ISNUMBER: 'ISNUMBER', ISODD: 'ISODD', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index dcd0933350..cc2541b014 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -82,6 +82,7 @@ export const dictionary: RawTranslationPackage = { ISERROR: 'ESERROR', ISEVEN: 'ES.PAR', ISLOGICAL: 'ESLOGICO', + ISNA: 'ESNOD', ISNONTEXT: 'ESNOTEXTO', ISNUMBER: 'ESNUMERO', ISODD: 'ES.IMPAR', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 9ffdc12d6d..b6c4912711 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERROR: 'ONVIRHE', ISEVEN: 'ONPARILLINEN', ISLOGICAL: 'ONTOTUUS', + ISNA: 'ONPUUTTUU', ISNONTEXT: 'ONEI_TEKSTI', ISNUMBER: 'ONLUKU', ISODD: 'ONPARITON', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index a51f89ff49..50e59da077 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERROR: 'ESTERREUR', ISEVEN: 'EST.PAIR', ISLOGICAL: 'ESTLOGIQUE', + ISNA: 'ESTNA', ISNONTEXT: 'ESTNONTEXTE', ISNUMBER: 'ESTNUM', ISODD: 'EST.IMPAIR', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index 116d791e25..cef8f440eb 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERROR: 'HIBÁS', ISEVEN: 'PÁROSE', ISLOGICAL: 'LOGIKAI', + ISNA: 'NINCS', ISNONTEXT: 'NEM.SZÖVEG', ISNUMBER: 'SZÁM', ISODD: 'PÁRATLANE', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index 4a29d165b0..82ccc34bc8 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERROR: 'VAL.ERRORE', ISEVEN: 'VAL.PARI', ISLOGICAL: 'VAL.LOGICO', + ISNA: 'VAL.NON.DISP', ISNONTEXT: 'VAL.NON.TESTO', ISNUMBER: 'VAL.NUMERO', ISODD: 'VAL.DISPARI', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index 5ccc7dd291..2aceb95c17 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERROR: 'ERFEIL', ISEVEN: 'ERPARTALL', ISLOGICAL: 'ERLOGISK', + ISNA: 'ERIT', ISNONTEXT: 'ERIKKETEKST', ISNUMBER: 'ERTALL', ISODD: 'ERODDE', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index a3739f2d80..a3ad208f2d 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERROR: 'ISFOUT', ISEVEN: 'IS.EVEN', ISLOGICAL: 'ISLOGISCH', + ISNA: 'ISNB', ISNONTEXT: 'ISGEENTEKST', ISNUMBER: 'ISGETAL', ISODD: 'IS.ONEVEN', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index df5c5af512..d9953d2504 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERROR: 'CZY.BŁĄD', ISEVEN: 'CZY.PARZYSTE', ISLOGICAL: 'CZY.LOGICZNA', + ISNA: 'CZY.BRAK', ISNONTEXT: 'CZY.NIE.TEKST', ISNUMBER: 'CZY.LICZBA', ISODD: 'CZY.NIEPARZYSTE', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index b961e9ac4d..9c609c597f 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERROR: 'ÉERROS', ISEVEN: 'ÉPAR', ISLOGICAL: 'ÉLÓGICO', + ISNA: 'É.NÃO.DISP', ISNONTEXT: 'É.NÃO.TEXTO', ISNUMBER: 'ÉNÚM', ISODD: 'ÉIMPAR', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index 2938048919..936b401e56 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERROR: 'ЕОШИБКА', ISEVEN: 'ЕЧЁТН', ISLOGICAL: 'ЕЛОГИЧ', + ISNA: 'ЕНД', ISNONTEXT: 'ЕНЕТЕКСТ', ISNUMBER: 'ЕЧИСЛО', ISODD: 'ЕНЕЧЁТ', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index 04007f77ba..40ce4b1403 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERROR: 'ÄRFEL', ISEVEN: 'ÄRJÄMN', ISLOGICAL: 'ÄRLOGISK', + ISNA: 'ÄRSAKNAD', ISNONTEXT: 'ÄREJTEXT', ISNUMBER: 'ÄRTAL', ISODD: 'ÄRUDDA', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index a85505437d..e54cffd56c 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERROR: 'EHATALIYSA', ISEVEN: 'ÇİFTMİ', ISLOGICAL: 'EMANTIKSALSA', + ISNA: 'EYOKSA', ISNONTEXT: 'EMETİNDEĞİLSE', ISNUMBER: 'ESAYIYSA', ISODD: 'TEKMİ', diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index de5a6e9972..49bf14bfb5 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -38,6 +38,12 @@ export class InformationPlugin extends FunctionPlugin { ] } }, + 'ISNA': { + method: 'isna', + parameters: [ + { argumentType: 'scalar'} + ] + }, 'ISNUMBER': { method: 'isnumber', parameters: { @@ -127,6 +133,20 @@ export class InformationPlugin extends FunctionPlugin { ) } + /** + * Corresponds to ISNA(value) + * + * Returns true if provided value is #N/A! error + * + * @param ast + * @param formulaAddress + */ + public isna(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISNA.parameters, (arg: InternalScalarValue) => + (arg instanceof CellError && arg.type == ErrorType.NA) + ) + } + /** * Corresponds to ISNUMBER(value) * diff --git a/test/interpreter/function-isna.spec.ts b/test/interpreter/function-isna.spec.ts new file mode 100644 index 0000000000..954892e916 --- /dev/null +++ b/test/interpreter/function-isna.spec.ts @@ -0,0 +1,41 @@ +import {HyperFormula} from '../../src' +import {ErrorType} from '../../src/Cell' +import {adr, detailedError} from '../testUtils' + +describe('Function ISNA', () => { + it('should return true for #NA! error', () => { + const engine = HyperFormula.buildFromArray([ + ['=TRUE(1)', '=ISNA(A1)', '=ISNA(TRUE(1))'], + ]) + + expect(engine.getCellValue(adr('B1'))).toEqual(true) + expect(engine.getCellValue(adr('C1'))).toEqual(true) + }) + + it('should return false for other values', () => { + const engine = HyperFormula.buildFromArray([ + ['=ISNA(1)', '=ISNA(TRUE())', '=ISNA("foo")', '=ISNA(A1)'], + ]) + expect(engine.getCellValue(adr('A1'))).toEqual(false) + expect(engine.getCellValue(adr('B1'))).toEqual(false) + expect(engine.getCellValue(adr('C1'))).toEqual(false) + expect(engine.getCellValue(adr('D1'))).toEqual(false) + }) + + it('takes exactly one argument', () => { + const engine = HyperFormula.buildFromArray([ + ['=ISNA(1, 2)', '=ISNA()'], + ]) + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NA)) + expect(engine.getCellValue(adr('B1'))).toEqual(detailedError(ErrorType.NA)) + }) + + // Inconsistency with Product 1 + it('range value results in VALUE error', () => { + const engine = HyperFormula.buildFromArray([ + ['=TRUE(1)'], + ['=TRUE(1)', '=ISNA(A1:A2)'], + ]) + expect(engine.getCellValue(adr('B2'))).toEqual(detailedError(ErrorType.VALUE)) + }) +}) From 7c33f07f148f88d635b7fdd6acebe2a043b5fa46 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Fri, 24 Jul 2020 13:42:40 +0200 Subject: [PATCH 74/97] Function ISREF --- docs/guide/built-in-functions.md | 1 + src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + src/interpreter/plugin/InformationPlugin.ts | 21 ++++++++ test/interpreter/function-isref.spec.ts | 59 +++++++++++++++++++++ 19 files changed, 97 insertions(+) create mode 100644 test/interpreter/function-isref.spec.ts diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index 99a2384c65..1c6886a857 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -90,6 +90,7 @@ lets you design your own [custom functions](custom-functions). | ISNONTEXT | Information | Tests if the cell contents are text or numbers, and returns FALSE if the contents are text. | ISNONTEXT(Value) | | ISNUMBER | Information | Returns TRUE if the value refers to a number. | ISNUMBER(Value) | | ISODD | Information | Returns TRUE if the value is odd, or FALSE if the number is even. | ISODD(Value) | +| ISREF | Information | Returns TRUE if provided value is #REF! error.| ISREF(Value) | | ISTEXT | Information | Returns TRUE if the cell contents refer to text.| ISTEXT(Value) | | FV | Financial | Returns the future value of an investment. | FV(Rate; Nper; Pmt[; Pv;[ Type]]) | | IPMT | Financial | Calculates the interest portion of a given loan payment in a given payment period. | IPMT(Rate; Per; Nper; Pv[; Fv[; Type]]) | diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index e533deea9d..42d0ee10f0 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -86,6 +86,7 @@ const dictionary: RawTranslationPackage = { ISNONTEXT: 'JE.NETEXT', ISNUMBER: 'JE.ČISLO', ISODD: 'ISODD', + ISREF: 'JE.ODKAZ', ISTEXT: 'JE.TEXT', LEFT: 'ZLEVA', LEN: 'DÉLKA', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 35a449c450..d7a6cdf2db 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -86,6 +86,7 @@ const dictionary: RawTranslationPackage = { ISNONTEXT: 'ER.IKKE.TEKST', ISNUMBER: 'ER.TAL', ISODD: 'ER.ULIGE', + ISREF: 'ER.REFERENCE', ISTEXT: 'ER.TEKST', LEFT: 'VENSTRE', LEN: 'LÆNGDE', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index fb0f483737..c27f63e90a 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -86,6 +86,7 @@ const dictionary: RawTranslationPackage = { ISNONTEXT: 'ISTKTEXT', ISNUMBER: 'ISTZAHL', ISODD: 'ISTUNGERADE', + ISREF: 'ISTBEZUG', ISTEXT: 'ISTTEXT', LEFT: 'LINKS', LEN: 'LÄNGE', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index ff780f3da2..d4253cb72d 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -86,6 +86,7 @@ const dictionary: RawTranslationPackage = { ISNONTEXT: 'ISNONTEXT', ISNUMBER: 'ISNUMBER', ISODD: 'ISODD', + ISREF: 'ISREF', ISTEXT: 'ISTEXT', LEFT: 'LEFT', LEN: 'LEN', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index cc2541b014..75525d9797 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -86,6 +86,7 @@ export const dictionary: RawTranslationPackage = { ISNONTEXT: 'ESNOTEXTO', ISNUMBER: 'ESNUMERO', ISODD: 'ES.IMPAR', + ISREF: 'ESREF', ISTEXT: 'ESTEXTO', LEFT: 'IZQUIERDA', LEN: 'LARGO', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index b6c4912711..27fbe3092d 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -86,6 +86,7 @@ const dictionary: RawTranslationPackage = { ISNONTEXT: 'ONEI_TEKSTI', ISNUMBER: 'ONLUKU', ISODD: 'ONPARITON', + ISREF: 'ONVIITT', ISTEXT: 'ONTEKSTI', LEFT: 'VASEN', LEN: 'PITUUS', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index 50e59da077..16f4547a2f 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -86,6 +86,7 @@ const dictionary: RawTranslationPackage = { ISNONTEXT: 'ESTNONTEXTE', ISNUMBER: 'ESTNUM', ISODD: 'EST.IMPAIR', + ISREF: 'ESTREF', ISTEXT: 'ESTTEXTE', LEFT: 'GAUCHE', LEN: 'NBCAR', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index cef8f440eb..097cf5b005 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -86,6 +86,7 @@ const dictionary: RawTranslationPackage = { ISNONTEXT: 'NEM.SZÖVEG', ISNUMBER: 'SZÁM', ISODD: 'PÁRATLANE', + ISREF: 'HIVATKOZÁS', ISTEXT: 'SZÖVEG.E', LEFT: 'BAL', LEN: 'HOSSZ', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index 82ccc34bc8..b8f50b2f36 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -86,6 +86,7 @@ const dictionary: RawTranslationPackage = { ISNONTEXT: 'VAL.NON.TESTO', ISNUMBER: 'VAL.NUMERO', ISODD: 'VAL.DISPARI', + ISREF: 'VAL.RIF', ISTEXT: 'VAL.TESTO', LEFT: 'SINISTRA', LEN: 'LUNGHEZZA', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index 2aceb95c17..c4442a09f2 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -86,6 +86,7 @@ const dictionary: RawTranslationPackage = { ISNONTEXT: 'ERIKKETEKST', ISNUMBER: 'ERTALL', ISODD: 'ERODDE', + ISREF: 'ERREF', ISTEXT: 'ERTEKST', LEFT: 'VENSTRE', LEN: 'LENGDE', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index a3ad208f2d..d00dbb76b3 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -86,6 +86,7 @@ const dictionary: RawTranslationPackage = { ISNONTEXT: 'ISGEENTEKST', ISNUMBER: 'ISGETAL', ISODD: 'IS.ONEVEN', + ISREF: 'ISVERWIJZING', ISTEXT: 'ISTEKST', LEFT: 'LINKS', LEN: 'PITUUS', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index d9953d2504..8af319aa04 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -86,6 +86,7 @@ const dictionary: RawTranslationPackage = { ISNONTEXT: 'CZY.NIE.TEKST', ISNUMBER: 'CZY.LICZBA', ISODD: 'CZY.NIEPARZYSTE', + ISREF: 'CZY.ADR', ISTEXT: 'CZY.TEKST', LEFT: 'LEWY', LEN: 'DŁ', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index 9c609c597f..1c14a7be21 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -86,6 +86,7 @@ const dictionary: RawTranslationPackage = { ISNONTEXT: 'É.NÃO.TEXTO', ISNUMBER: 'ÉNÚM', ISODD: 'ÉIMPAR', + ISREF: 'ÉREF', ISTEXT: 'ÉTEXTO', LEFT: 'ESQUERDA', LEN: 'NÚM.CARACT', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index 936b401e56..709c244fc4 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -86,6 +86,7 @@ const dictionary: RawTranslationPackage = { ISNONTEXT: 'ЕНЕТЕКСТ', ISNUMBER: 'ЕЧИСЛО', ISODD: 'ЕНЕЧЁТ', + ISREF: 'ЕССЫЛКА', ISTEXT: 'ЕТЕКСТ', LEFT: 'ЛЕВСИМВ', LEN: 'ДЛСТР', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index 40ce4b1403..665a3601a2 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -86,6 +86,7 @@ const dictionary: RawTranslationPackage = { ISNONTEXT: 'ÄREJTEXT', ISNUMBER: 'ÄRTAL', ISODD: 'ÄRUDDA', + ISREF: 'ÄRREF', ISTEXT: 'ÄRTEXT', LEFT: 'VÄNSTER', LEN: 'LÄNGD', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index e54cffd56c..06154a96d7 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -86,6 +86,7 @@ const dictionary: RawTranslationPackage = { ISNONTEXT: 'EMETİNDEĞİLSE', ISNUMBER: 'ESAYIYSA', ISODD: 'TEKMİ', + ISREF: 'EREFSE', ISTEXT: 'EMETİNSE', LEFT: 'SOL', LEN: 'UZUNLUK', diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index 49bf14bfb5..f6b5511a09 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -60,6 +60,12 @@ export class InformationPlugin extends FunctionPlugin { ] } }, + 'ISREF': { + method: 'isref', + parameters: [ + { argumentType: 'scalar'} + ] + }, 'ISTEXT': { method: 'istext', parameters: { @@ -175,6 +181,21 @@ export class InformationPlugin extends FunctionPlugin { ) } + /** + * Corresponds to ISREF(value) + * + * Returns true if provided value is #REF! error + * + * @param ast + * @param formulaAddress + */ + public isref(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISREF.parameters, (arg: InternalScalarValue) => + (arg instanceof CellError && (arg.type == ErrorType.REF || arg.type == ErrorType.CYCLE)) + ) + } + + /** * Corresponds to ISTEXT(value) * diff --git a/test/interpreter/function-isref.spec.ts b/test/interpreter/function-isref.spec.ts new file mode 100644 index 0000000000..7005c8b0e8 --- /dev/null +++ b/test/interpreter/function-isref.spec.ts @@ -0,0 +1,59 @@ +import {HyperFormula} from '../../src' +import {ErrorType} from '../../src/Cell' +import {adr, detailedError} from '../testUtils' + +describe('Function ISREF', () => { + it('should return true for #REF!', () => { + const engine = HyperFormula.buildFromArray([ + ['=#REF!', '=ISREF(A1)'], + ]) + + expect(engine.getCellValue(adr('B1'))).toEqual(true) + }) + + it('should return true for #CYCLE!', () => { + const engine = HyperFormula.buildFromArray([ + ['=A1', '=ISREF(A1)'], + ]) + + expect(engine.getCellValue(adr('B1'))).toEqual(true) + }) + + it('should return false for other values', () => { + const engine = HyperFormula.buildFromArray([ + ['=ISREF(1)', '=ISREF(TRUE())', '=ISREF("foo")', '=ISREF(A1)'], + ]) + expect(engine.getCellValue(adr('A1'))).toEqual(false) + expect(engine.getCellValue(adr('B1'))).toEqual(false) + expect(engine.getCellValue(adr('C1'))).toEqual(false) + expect(engine.getCellValue(adr('D1'))).toEqual(false) + }) + + it('takes exactly one argument', () => { + const engine = HyperFormula.buildFromArray([ + ['=ISREF(1, 2)', '=ISREF()'], + ]) + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NA)) + expect(engine.getCellValue(adr('B1'))).toEqual(detailedError(ErrorType.NA)) + }) + + // Inconsistency with Product 1 + it('range value results in VALUE error', () => { + const engine = HyperFormula.buildFromArray([ + ['=A1'], + ['=A2', '=ISREF(A1:A3)'], + ]) + + expect(engine.getCellValue(adr('B2'))).toEqual(detailedError(ErrorType.VALUE)) + }) + + // Inconsistency with Product 1 + it('returns #CYCLE! for itself', () => { + /* TODO can we handle such case correctly? */ + const engine = HyperFormula.buildFromArray([ + ['=ISREF(A1)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.CYCLE)) + }) +}) From 56fecd97193a773670b70fff4bbfe5fe67b6432a Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Fri, 24 Jul 2020 14:02:46 +0200 Subject: [PATCH 75/97] Function NA --- docs/guide/built-in-functions.md | 1 + src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + src/interpreter/plugin/InformationPlugin.ts | 15 +++++++++++++++ test/interpreter/functions-na.spec.ts | 13 +++++++++++++ 19 files changed, 45 insertions(+) create mode 100644 test/interpreter/functions-na.spec.ts diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index 1c6886a857..55437c392f 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -92,6 +92,7 @@ lets you design your own [custom functions](custom-functions). | ISODD | Information | Returns TRUE if the value is odd, or FALSE if the number is even. | ISODD(Value) | | ISREF | Information | Returns TRUE if provided value is #REF! error.| ISREF(Value) | | ISTEXT | Information | Returns TRUE if the cell contents refer to text.| ISTEXT(Value) | +| NA | Information | Returns #N/A! error value.| NA(Value) | | FV | Financial | Returns the future value of an investment. | FV(Rate; Nper; Pmt[; Pv;[ Type]]) | | IPMT | Financial | Calculates the interest portion of a given loan payment in a given payment period. | IPMT(Rate; Per; Nper; Pv[; Fv[; Type]]) | | PMT | Financial | Returns the periodic payment for a loan. | PMT(Rate; Nper; Pv[; Fv[; Type]]) | diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index 42d0ee10f0..338003175f 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -104,6 +104,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'SOUČIN.MATIC', MOD: 'MOD', MONTH: 'MĚSÍC', + NA: 'NEDEF', NOT: 'NE', ODD: 'ZAOKROUHLIT.NA.LICHÉ', OFFSET: 'POSUN', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index d7a6cdf2db..f1419177bf 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -104,6 +104,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'MPRODUKT', MOD: 'REST', MONTH: 'MÅNED', + NA: 'IKKE.TILGÆNGELIG', NOT: 'IKKE', ODD: 'ULIGE', OFFSET: 'FORSKYDNING', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index c27f63e90a..2969006ef6 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -104,6 +104,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'MMULT', MOD: 'REST', MONTH: 'MONAT', + NA: 'NV', NOT: 'NICHT', ODD: 'UNGERADE', OFFSET: 'BEREICH.VERSCHIEBEN', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index d4253cb72d..a38d3a5be8 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -104,6 +104,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'MMULT', MOD: 'MOD', MONTH: 'MONTH', + NA: 'NA', NOT: 'NOT', ODD: 'ODD', OFFSET: 'OFFSET', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index 75525d9797..49983e8857 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -104,6 +104,7 @@ export const dictionary: RawTranslationPackage = { MMULT: 'MMULT', MOD: 'RESIDUO', MONTH: 'MES', + NA: 'NOD', NOT: 'NO', ODD: 'REDONDEA.IMPAR', OFFSET: 'DESREF', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 27fbe3092d..dc990f22ba 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -104,6 +104,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'MKERRO', MOD: 'JAKOJ', MONTH: 'KUUKAUSI', + NA: 'PUUTTUU', NOT: 'EI', ODD: 'PARITON', OFFSET: 'SIIRTYMÄ', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index 16f4547a2f..4367458fda 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -104,6 +104,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'PRODUITMAT', MOD: 'MOD', MONTH: 'MOIS', + NA: 'NA', NOT: 'NON', ODD: 'IMPAIR', OFFSET: 'DECALER', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index 097cf5b005..83b9f5c20f 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -104,6 +104,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'MSZORZAT', MOD: 'MARADÉK', MONTH: 'HÓNAP', + NA: 'HIÁNYZIK', NOT: 'NEM', ODD: 'PÁRATLAN', OFFSET: 'ELTOLÁS', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index b8f50b2f36..ff37fb6733 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -104,6 +104,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'MATR.PRODOTTO', MOD: 'RESTO', MONTH: 'MESE', + NA: 'NON.DISP', NOT: 'NON', ODD: 'DISPARI', OFFSET: 'SCARTO', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index c4442a09f2..8d1fffd3c5 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -104,6 +104,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'MMULT', MOD: 'REST', MONTH: 'MÅNED', + NA: 'IT', NOT: 'IKKE', ODD: 'AVRUND.TIL.ODDETALL', OFFSET: 'FORSKYVNING', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index d00dbb76b3..ca3d62611f 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -104,6 +104,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'PRODUCTMAT', MOD: 'REST', MONTH: 'MAAND', + NA: 'NB', NOT: 'NIET', ODD: 'ONEVEN', OFFSET: 'VERSCHUIVING', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index 8af319aa04..6fc6837597 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -104,6 +104,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'MACIERZ.ILOCZYN', MOD: 'MOD', MONTH: 'MIESIĄC', + NA: 'BRAK', NOT: 'NIE', ODD: 'ZAOKR.DO.NPARZ', OFFSET: 'PRZESUNIĘCIE', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index 1c14a7be21..0e8c14030b 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -104,6 +104,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'MATRIZ.MULT', MOD: 'MOD', MONTH: 'MÊS', + NA: 'NÃO.DISP', NOT: 'NÃO', ODD: 'ÍMPAR', OFFSET: 'DESLOC', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index 709c244fc4..0cf52209a3 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -104,6 +104,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'МУМНОЖ', MOD: 'ОСТАТ', MONTH: 'МЕСЯЦ', + NA: 'НД', NOT: 'НЕ', ODD: 'НЕЧЁТ', OFFSET: 'СМЕЩ', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index 665a3601a2..44e947f7f2 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -104,6 +104,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'MMULT', MOD: 'REST', MONTH: 'MÅNAD', + NA: 'SAKNAS', NOT: 'ICKE', ODD: 'UDDA', OFFSET: 'FÖRSKJUTNING', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index 06154a96d7..1f22798e01 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -104,6 +104,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'DÇARP', MOD: 'MOD', MONTH: 'AY', + NA: 'YOKSAY', NOT: 'DEĞİL', ODD: 'TEK', OFFSET: 'KAYDIR', diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index f6b5511a09..b1578a984c 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -94,6 +94,9 @@ export class InformationPlugin extends FunctionPlugin { }, 'INDEX': { method: 'index', + }, + 'NA': { + method: 'na' } } @@ -311,4 +314,16 @@ export class InformationPlugin extends FunctionPlugin { const address = range.getAddress(columnValue - 1, rowValue - 1) return this.dependencyGraph.getCellValue(address) } + + /** + * Corresponds to NA() + * + * Returns #N/A! + * + * @param _ast + * @param _formulaAddress + */ + public na(_ast: ProcedureAst, _formulaAddress: SimpleCellAddress): InternalCellValue { + return new CellError(ErrorType.NA) + } } diff --git a/test/interpreter/functions-na.spec.ts b/test/interpreter/functions-na.spec.ts new file mode 100644 index 0000000000..f175439108 --- /dev/null +++ b/test/interpreter/functions-na.spec.ts @@ -0,0 +1,13 @@ +import {ErrorType, HyperFormula} from '../../src' +import {adr, detailedError} from '../testUtils' + +describe('Function NA', () => { + it('should work', () => { + const engine = HyperFormula.buildFromArray([ + ['=NA()', '=NA(1)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NA)) + expect(engine.getCellValue(adr('B1'))).toEqual(detailedError(ErrorType.NA)) + }) +}) \ No newline at end of file From b01130a8016a39caf90afd687555230f88f3cb3f Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Fri, 24 Jul 2020 14:05:42 +0200 Subject: [PATCH 76/97] Template for noarg function --- src/interpreter/plugin/InformationPlugin.ts | 22 ++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index b1578a984c..7f778c233a 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -40,9 +40,11 @@ export class InformationPlugin extends FunctionPlugin { }, 'ISNA': { method: 'isna', - parameters: [ - { argumentType: 'scalar'} - ] + parameters: { + list: [ + {argumentType: 'scalar'} + ] + } }, 'ISNUMBER': { method: 'isnumber', @@ -62,9 +64,11 @@ export class InformationPlugin extends FunctionPlugin { }, 'ISREF': { method: 'isref', - parameters: [ - { argumentType: 'scalar'} - ] + parameters: { + list: [ + {argumentType: 'scalar'} + ] + } }, 'ISTEXT': { method: 'istext', @@ -151,7 +155,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public isna(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISNA.parameters, (arg: InternalScalarValue) => + return this.runFunction(ast.args, formulaAddress, this.parameters('ISNA'), (arg: InternalScalarValue) => (arg instanceof CellError && arg.type == ErrorType.NA) ) } @@ -193,7 +197,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public isref(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISREF.parameters, (arg: InternalScalarValue) => + return this.runFunction(ast.args, formulaAddress, this.parameters('ISREF'), (arg: InternalScalarValue) => (arg instanceof CellError && (arg.type == ErrorType.REF || arg.type == ErrorType.CYCLE)) ) } @@ -323,7 +327,7 @@ export class InformationPlugin extends FunctionPlugin { * @param _ast * @param _formulaAddress */ - public na(_ast: ProcedureAst, _formulaAddress: SimpleCellAddress): InternalCellValue { + public na(_ast: ProcedureAst, _formulaAddress: SimpleCellAddress): CellError { return new CellError(ErrorType.NA) } } From a91bf23fee181063846e9dcf6f10ba6901c901cf Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Fri, 24 Jul 2020 15:36:53 +0200 Subject: [PATCH 77/97] Function SHEET --- docs/guide/built-in-functions.md | 5 +- src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + src/interpreter/plugin/InformationPlugin.ts | 83 +++++++++++++++++---- test/interpreter/function-sheet.spec.ts | 81 ++++++++++++++++++++ 19 files changed, 168 insertions(+), 17 deletions(-) create mode 100644 test/interpreter/function-sheet.spec.ts diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index 55437c392f..fc07166ddd 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -90,8 +90,9 @@ lets you design your own [custom functions](custom-functions). | ISNONTEXT | Information | Tests if the cell contents are text or numbers, and returns FALSE if the contents are text. | ISNONTEXT(Value) | | ISNUMBER | Information | Returns TRUE if the value refers to a number. | ISNUMBER(Value) | | ISODD | Information | Returns TRUE if the value is odd, or FALSE if the number is even. | ISODD(Value) | -| ISREF | Information | Returns TRUE if provided value is #REF! error.| ISREF(Value) | -| ISTEXT | Information | Returns TRUE if the cell contents refer to text.| ISTEXT(Value) | +| ISREF | Information | Returns TRUE if provided value is #REF! error. | ISREF(Value) | +| ISTEXT | Information | Returns TRUE if the cell contents refer to text. | ISTEXT(Value) | +| SHEET | Information | Returns sheet number of a given value or a formula sheet number if no argument is provided. | SHEET([Value]) | | NA | Information | Returns #N/A! error value.| NA(Value) | | FV | Financial | Returns the future value of an investment. | FV(Rate; Nper; Pmt[; Pv;[ Type]]) | | IPMT | Financial | Calculates the interest portion of a given loan payment in a given payment period. | IPMT(Rate; Per; Nper; Pv[; Fv[; Type]]) | diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index 338003175f..ea24e18e5a 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'ROUNDUP', ROWS: 'ŘÁDKY', SEARCH: 'HLEDAT', + SHEET: 'SHEET', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'ODMOCNINA', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index f1419177bf..df73dbedca 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'RUND.OP', ROWS: 'RÆKKER', SEARCH: 'SØG', + SHEET: 'ARK', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'KVROD', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index 2969006ef6..8550f68526 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'AUFRUNDEN', ROWS: 'ZEILEN', SEARCH: 'SUCHEN', + SHEET: 'BLATT', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'WURZEL', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index a38d3a5be8..b70343e5e8 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'ROUNDUP', ROWS: 'ROWS', SEARCH: 'SEARCH', + SHEET: 'SHEET', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'SQRT', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index 49983e8857..f56de563ba 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -123,6 +123,7 @@ export const dictionary: RawTranslationPackage = { ROUNDUP: 'REDONDEAR.MAS', ROWS: 'FILAS', SEARCH: 'HALLAR', + SHEET: 'HOJA', SIN: 'SENO', SPLIT: 'SPLIT', SQRT: 'RAIZ', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index dc990f22ba..526e9189df 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'PYÖRISTÄ.DES.YLÖS', ROWS: 'RIVIT', SEARCH: 'KÄY.LÄPI', + SHEET: 'TAULUKKO', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'NELIÖJUURI', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index 4367458fda..1ea001bd47 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'ARRONDI.SUP', ROWS: 'LIGNES', SEARCH: 'CHERCHE', + SHEET: 'FEUILLE', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'RACINE', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index 83b9f5c20f..b3b43f57c5 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'KEREK.FEL', ROWS: 'SOROK', SEARCH: 'SZÖVEG.KERES', + SHEET: 'LAP', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'GYÖK', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index ff37fb6733..5a249bcab5 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'ARROTONDA.PER.ECC', ROWS: 'RIGHE', SEARCH: 'RICERCA', + SHEET: 'FOGLIO', SIN: 'SEN', SPLIT: 'SPLIT', SQRT: 'RADQ', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index 8d1fffd3c5..9991644ce7 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'AVRUND.OPP', ROWS: 'RADER', SEARCH: 'SØK', + SHEET: 'ARK', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'ROT', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index ca3d62611f..6d0937883c 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'AFRONDEN.NAAR.BOVEN', ROWS: 'RIJEN', SEARCH: 'VIND.SPEC', + SHEET: 'BLAD', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'WORTEL', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index 6fc6837597..deeb6acd25 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'ZAOKR.GÓRA', ROWS: 'ILE.WIERSZY', SEARCH: 'SZUKAJ.TEKST', + SHEET: 'ARKUSZ', SIN: 'SIN', SPLIT: 'PODZIEL.TEKST', SQRT: 'PIERWIASTEK', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index 0e8c14030b..382bb7a4cd 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'ARREDONDAR.PARA.CIMA', ROWS: 'LINS', SEARCH: 'LOCALIZAR', + SHEET: 'PLANILHA', SIN: 'SEN', SPLIT: 'SPLIT', SQRT: 'RAIZ', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index 0cf52209a3..40660839ab 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'ОКРУГЛВВЕРХ', ROWS: 'ЧСТРОК', SEARCH: 'ПОИСК', + SHEET: 'ЛИСТ', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'КОРЕНЬ', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index 44e947f7f2..c8800b37f2 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'AVRUNDA.UPPÅT', ROWS: 'RADER', SEARCH: 'SÖK', + SHEET: 'SHEET', SIN: 'SIN', SPLIT: 'SPLIT', SQRT: 'ROT', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index 1f22798e01..6941a3067e 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'YUKARIYUVARLA', ROWS: 'SATIRSAY', SEARCH: 'MBUL', + SHEET: 'SAYFA', SIN: 'SİN', SPLIT: 'SPLIT', SQRT: 'KAREKÖK', diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index 7f778c233a..26f4a50800 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -8,30 +8,36 @@ import {CellError, EmptyValue, ErrorType, InternalScalarValue, SimpleCellAddress import {AstNodeType, ProcedureAst} from '../../parser' import {InterpreterValue} from '../InterpreterValue' import {FunctionPlugin} from './FunctionPlugin' +import {Maybe} from '../../Maybe' /** * Interpreter plugin containing information functions */ export class InformationPlugin extends FunctionPlugin { public static implementedFunctions = { - 'ISERR': { - method: 'iserr', + 'COLUMNS': { + method: 'columns', + isDependentOnSheetStructureChange: true, + doesNotNeedArgumentsToBeComputed: true, + }, + 'ISBLANK': { + method: 'isblank', parameters: { list: [ {argumentType: 'scalar'} ] } }, - 'ISERROR': { - method: 'iserror', + 'ISERR': { + method: 'iserr', parameters: { list: [ {argumentType: 'scalar'} ] } }, - 'ISBLANK': { - method: 'isblank', + 'ISERROR': { + method: 'iserror', parameters: { list: [ {argumentType: 'scalar'} @@ -86,21 +92,25 @@ export class InformationPlugin extends FunctionPlugin { ] } }, - 'COLUMNS': { - method: 'columns', - isDependentOnSheetStructureChange: true, - doesNotNeedArgumentsToBeComputed: true, + 'INDEX': { + method: 'index', + }, + 'NA': { + method: 'na' }, 'ROWS': { method: 'rows', isDependentOnSheetStructureChange: true, doesNotNeedArgumentsToBeComputed: true, }, - 'INDEX': { - method: 'index', - }, - 'NA': { - method: 'na' + 'SHEET': { + method: 'sheet', + parameters: { + list: [ + {argumentType: 'noerror'} + ] + }, + doesNotNeedArgumentsToBeComputed: true } } @@ -330,4 +340,47 @@ export class InformationPlugin extends FunctionPlugin { public na(_ast: ProcedureAst, _formulaAddress: SimpleCellAddress): CellError { return new CellError(ErrorType.NA) } + + /** + * Corresponds to SHEET(value) + * + * Returns sheet number of a given value or a formula sheet number if no argument is provided + * + * @param ast + * @param formulaAddress + * */ + public sheet(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + if (ast.args.length > 1) { + return new CellError(ErrorType.NA) + } else if (ast.args.length == 0) { + return formulaAddress.sheet + 1 + } + const arg = ast.args[0] + + let cellReference: Maybe + + if (arg.type === AstNodeType.CELL_REFERENCE) { + cellReference = arg.reference.toSimpleCellAddress(formulaAddress) + } else if (arg.type === AstNodeType.CELL_RANGE || arg.type === AstNodeType.COLUMN_RANGE || arg.type === AstNodeType.ROW_RANGE) { + try { + cellReference = AbsoluteCellRange.fromAst(arg, formulaAddress).start + } catch (e) { + return new CellError(ErrorType.REF) + } + } + + if (cellReference !== undefined) { + return cellReference.sheet + 1 + } + + /* Not using static parameters definition as we expect only values coerced to string from now on. */ + return this.runFunction(ast.args, formulaAddress, { list: [{ argumentType: 'string' }]}, (value: string) => { + const sheetNumber = this.dependencyGraph.sheetMapping.get(value) + if (sheetNumber !== undefined) { + return sheetNumber + 1 + } else { + return new CellError(ErrorType.NA) + } + }) + } } diff --git a/test/interpreter/function-sheet.spec.ts b/test/interpreter/function-sheet.spec.ts new file mode 100644 index 0000000000..7018a397f3 --- /dev/null +++ b/test/interpreter/function-sheet.spec.ts @@ -0,0 +1,81 @@ +import {ErrorType, HyperFormula} from '../../src' +import {adr, detailedError} from '../testUtils' + +describe('Function SHEET', () => { + it('should return formula sheet number', () => { + const engine = HyperFormula.buildFromSheets({ + 'Sheet1': [['=SHEET()']], + 'Sheet2': [['=SHEET()']], + }) + + expect(engine.getCellValue(adr('A1'))).toEqual(1) + expect(engine.getCellValue(adr('A1', 1))).toEqual(2) + }) + + it('should return reference sheet number for self sheet reference', () => { + const engine = HyperFormula.buildFromSheets({ + 'Sheet1': [['=SHEET(B1)']], + 'Sheet2': [['=SHEET(B1)', '=1/0']], + }) + + expect(engine.getCellValue(adr('A1'))).toEqual(1) + expect(engine.getCellValue(adr('A1', 1))).toEqual(2) + }) + + it('should return reference sheet number for absolute sheet reference', () => { + const engine = HyperFormula.buildFromSheets({ + 'Sheet1': [['=SHEET(Sheet1!B1)', '=SHEET(Sheet2!B1)']], + 'Sheet2': [['=SHEET(Sheet1!B1)', '=SHEET(Sheet2!B2)']], + }) + + expect(engine.getCellValue(adr('A1'))).toEqual(1) + expect(engine.getCellValue(adr('B1'))).toEqual(2) + expect(engine.getCellValue(adr('A1', 1))).toEqual(1) + expect(engine.getCellValue(adr('B1', 1))).toEqual(2) + }) + + it('should return range sheet number', () => { + const engine = HyperFormula.buildFromSheets({ + 'Sheet1': [['=SHEET(B1:B2)', '=SHEET(Sheet2!A1:B1)']], + 'Sheet2': [['=SHEET(B1:B2)', '=SHEET(Sheet1!A1:B1)']], + }) + + expect(engine.getCellValue(adr('A1'))).toEqual(1) + expect(engine.getCellValue(adr('B1'))).toEqual(2) + expect(engine.getCellValue(adr('A1', 1))).toEqual(2) + expect(engine.getCellValue(adr('B1', 1))).toEqual(1) + }) + + it('should return VALUE for non existing sheet', () => { + const engine = HyperFormula.buildFromSheets({ + 'Sheet1': [['=SHEET("FOO")', '=SHEET(1)']], + }) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NA)) + expect(engine.getCellValue(adr('B1'))).toEqual(detailedError(ErrorType.NA)) + }) + + it('should coerce', () => { + const engine = HyperFormula.buildFromArray([]) + engine.addSheet('TRUE') + engine.addSheet('1') + + engine.setCellContents(adr('A1'), [['=SHEET(1=1)']]) + engine.setCellContents(adr('B1'), [['=SHEET(1)']]) + + expect(engine.getCellValue(adr('A1'))).toEqual(2) + expect(engine.getCellValue(adr('B1'))).toEqual(3) + }) + + it('should propagate errors', () => { + const engine = HyperFormula.buildFromArray([['=SHEET(1/0)']]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.DIV_BY_ZERO)) + }) + + it('should work for itself', () => { + const engine = HyperFormula.buildFromArray([['=SHEET(A1)']]) + + expect(engine.getCellValue(adr('A1'))).toEqual(1) + }) +}) \ No newline at end of file From 6cb84edc34b51658e1f2d55bcbf22d8abaac1dc6 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Fri, 24 Jul 2020 16:09:28 +0200 Subject: [PATCH 78/97] Function SHEETS --- docs/guide/built-in-functions.md | 1 + src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + src/interpreter/plugin/InformationPlugin.ts | 42 ++++++++++++++++++++- test/interpreter/function-sheets.spec.ts | 42 +++++++++++++++++++++ 19 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 test/interpreter/function-sheets.spec.ts diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index fc07166ddd..101358c648 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -93,6 +93,7 @@ lets you design your own [custom functions](custom-functions). | ISREF | Information | Returns TRUE if provided value is #REF! error. | ISREF(Value) | | ISTEXT | Information | Returns TRUE if the cell contents refer to text. | ISTEXT(Value) | | SHEET | Information | Returns sheet number of a given value or a formula sheet number if no argument is provided. | SHEET([Value]) | +| SHEETS | Information | Returns number of sheet of a given reference or number of all sheets in workbook when no argument is provided. | SHEETS([Value]) | | NA | Information | Returns #N/A! error value.| NA(Value) | | FV | Financial | Returns the future value of an investment. | FV(Rate; Nper; Pmt[; Pv;[ Type]]) | | IPMT | Financial | Calculates the interest portion of a given loan payment in a given payment period. | IPMT(Rate; Per; Nper; Pv[; Fv[; Type]]) | diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index ea24e18e5a..14afe9818d 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'ROUNDUP', ROWS: 'ŘÁDKY', SEARCH: 'HLEDAT', + SHEETS: 'SHEETS', SHEET: 'SHEET', SIN: 'SIN', SPLIT: 'SPLIT', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index df73dbedca..00516ebb17 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'RUND.OP', ROWS: 'RÆKKER', SEARCH: 'SØG', + SHEETS: 'ARK.FLERE', SHEET: 'ARK', SIN: 'SIN', SPLIT: 'SPLIT', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index 8550f68526..b876702876 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'AUFRUNDEN', ROWS: 'ZEILEN', SEARCH: 'SUCHEN', + SHEETS: 'BLÄTTER', SHEET: 'BLATT', SIN: 'SIN', SPLIT: 'SPLIT', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index b70343e5e8..23bc9db587 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'ROUNDUP', ROWS: 'ROWS', SEARCH: 'SEARCH', + SHEETS: 'SHEETS', SHEET: 'SHEET', SIN: 'SIN', SPLIT: 'SPLIT', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index f56de563ba..d533cdff7f 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -123,6 +123,7 @@ export const dictionary: RawTranslationPackage = { ROUNDUP: 'REDONDEAR.MAS', ROWS: 'FILAS', SEARCH: 'HALLAR', + SHEETS: 'HOJAS', SHEET: 'HOJA', SIN: 'SENO', SPLIT: 'SPLIT', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 526e9189df..9cdb6c9283 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'PYÖRISTÄ.DES.YLÖS', ROWS: 'RIVIT', SEARCH: 'KÄY.LÄPI', + SHEETS: 'TAULUKOT', SHEET: 'TAULUKKO', SIN: 'SIN', SPLIT: 'SPLIT', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index 1ea001bd47..7126818b8d 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'ARRONDI.SUP', ROWS: 'LIGNES', SEARCH: 'CHERCHE', + SHEETS: 'FEUILLES', SHEET: 'FEUILLE', SIN: 'SIN', SPLIT: 'SPLIT', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index b3b43f57c5..de18289d59 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'KEREK.FEL', ROWS: 'SOROK', SEARCH: 'SZÖVEG.KERES', + SHEETS: 'LAPOK', SHEET: 'LAP', SIN: 'SIN', SPLIT: 'SPLIT', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index 5a249bcab5..9c6313078d 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'ARROTONDA.PER.ECC', ROWS: 'RIGHE', SEARCH: 'RICERCA', + SHEETS: 'FOGLI', SHEET: 'FOGLIO', SIN: 'SEN', SPLIT: 'SPLIT', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index 9991644ce7..bea65ec68a 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'AVRUND.OPP', ROWS: 'RADER', SEARCH: 'SØK', + SHEETS: 'SHEETS', SHEET: 'ARK', SIN: 'SIN', SPLIT: 'SPLIT', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index 6d0937883c..05d2b7c451 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'AFRONDEN.NAAR.BOVEN', ROWS: 'RIJEN', SEARCH: 'VIND.SPEC', + SHEETS: 'BLADEN', SHEET: 'BLAD', SIN: 'SIN', SPLIT: 'SPLIT', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index deeb6acd25..fb9c40b00e 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'ZAOKR.GÓRA', ROWS: 'ILE.WIERSZY', SEARCH: 'SZUKAJ.TEKST', + SHEETS: 'ARKUSZE', SHEET: 'ARKUSZ', SIN: 'SIN', SPLIT: 'PODZIEL.TEKST', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index 382bb7a4cd..e4b8bd383f 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'ARREDONDAR.PARA.CIMA', ROWS: 'LINS', SEARCH: 'LOCALIZAR', + SHEETS: 'PLANILHAS', SHEET: 'PLANILHA', SIN: 'SEN', SPLIT: 'SPLIT', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index 40660839ab..a977117c39 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'ОКРУГЛВВЕРХ', ROWS: 'ЧСТРОК', SEARCH: 'ПОИСК', + SHEETS: 'ЛИСТЫ', SHEET: 'ЛИСТ', SIN: 'SIN', SPLIT: 'SPLIT', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index c8800b37f2..62fcc8ab2d 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'AVRUNDA.UPPÅT', ROWS: 'RADER', SEARCH: 'SÖK', + SHEETS: 'SHEETS', SHEET: 'SHEET', SIN: 'SIN', SPLIT: 'SPLIT', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index 6941a3067e..ca77c02c71 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -123,6 +123,7 @@ const dictionary: RawTranslationPackage = { ROUNDUP: 'YUKARIYUVARLA', ROWS: 'SATIRSAY', SEARCH: 'MBUL', + SHEETS: 'SAYFALAR', SHEET: 'SAYFA', SIN: 'SİN', SPLIT: 'SPLIT', diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index 26f4a50800..b40594342b 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -111,6 +111,15 @@ export class InformationPlugin extends FunctionPlugin { ] }, doesNotNeedArgumentsToBeComputed: true + }, + 'SHEETS': { + method: 'sheets', + parameters: { + list: [ + {argumentType: 'noerror'} + ] + }, + doesNotNeedArgumentsToBeComputed: true } } @@ -374,7 +383,7 @@ export class InformationPlugin extends FunctionPlugin { } /* Not using static parameters definition as we expect only values coerced to string from now on. */ - return this.runFunction(ast.args, formulaAddress, { list: [{ argumentType: 'string' }]}, (value: string) => { + return this.runFunction(ast.args, formulaAddress, {list: [{argumentType: 'string'}]}, (value: string) => { const sheetNumber = this.dependencyGraph.sheetMapping.get(value) if (sheetNumber !== undefined) { return sheetNumber + 1 @@ -383,4 +392,35 @@ export class InformationPlugin extends FunctionPlugin { } }) } + + /** + * Corresponds to SHEETS(value) + * + * Returns number of sheet of a given reference or number of all sheets in workbook when no argument is provided. + * It returns always 1 for a valid reference as 3D references are not supported. + * + * @param ast + * @param formulaAddress + * */ + public sheets(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + if (ast.args.length > 1) { + return new CellError(ErrorType.NA) + } else if (ast.args.length == 0) { + return this.dependencyGraph.sheetMapping.numberOfSheets() + } + + const arg = ast.args[0] + if ( + arg.type === AstNodeType.CELL_REFERENCE || + arg.type === AstNodeType.CELL_RANGE || + arg.type === AstNodeType.COLUMN_RANGE || + arg.type === AstNodeType.ROW_RANGE + ) { + return 1 + } + + return this.runFunction(ast.args, formulaAddress, this.parameters('SHEETS'), () => { + return new CellError(ErrorType.VALUE) + }) + } } diff --git a/test/interpreter/function-sheets.spec.ts b/test/interpreter/function-sheets.spec.ts new file mode 100644 index 0000000000..00e1679f4b --- /dev/null +++ b/test/interpreter/function-sheets.spec.ts @@ -0,0 +1,42 @@ +import {ErrorType, HyperFormula} from '../../src' +import {adr, detailedError} from '../testUtils' + +describe('Function SHEETS', () => { + it('should return number of sheets if no argument provided', () => { + const engine = HyperFormula.buildFromSheets({ + 'Sheet1': [['=SHEETS()']], + 'Sheet2': [], + }) + + expect(engine.getCellValue(adr('A1'))).toEqual(2) + }) + + it('should return 1 for a valid reference', () => { + const engine = HyperFormula.buildFromArray([['=SHEETS(B1)', '=SHEETS(A1:A2)', '=SHEETS(A:B)', '=SHEETS(1:2)']]) + + expect(engine.getCellValue(adr('A1'))).toEqual(1) + expect(engine.getCellValue(adr('B1'))).toEqual(1) + expect(engine.getCellValue(adr('C1'))).toEqual(1) + expect(engine.getCellValue(adr('D1'))).toEqual(1) + }) + + it('should return VALUE for non-reference parameter', () => { + const engine = HyperFormula.buildFromArray([['=SHEETS(1)', '=SHEETS("foo")', '=SHEETS(TRUE())']]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.VALUE)) + expect(engine.getCellValue(adr('B1'))).toEqual(detailedError(ErrorType.VALUE)) + expect(engine.getCellValue(adr('C1'))).toEqual(detailedError(ErrorType.VALUE)) + }) + + it('should propagate errors', () => { + const engine = HyperFormula.buildFromArray([['=SHEETS(1/0)']]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.DIV_BY_ZERO)) + }) + + it('should work for itself', () => { + const engine = HyperFormula.buildFromArray([['=SHEETS(A1)']]) + + expect(engine.getCellValue(adr('A1'))).toEqual(1) + }) +}) \ No newline at end of file From 71e9d2ffd2ad7f0027b7c2adaf678b77520c91d5 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Sat, 25 Jul 2020 09:05:20 +0200 Subject: [PATCH 79/97] Function ISBINARY --- docs/guide/built-in-functions.md | 1 + src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + src/interpreter/plugin/InformationPlugin.ts | 20 +++++++++++++++++ test/interpreter/function-isbinary.spec.ts | 24 +++++++++++++++++++++ 19 files changed, 61 insertions(+) create mode 100644 test/interpreter/function-isbinary.spec.ts diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index 101358c648..912afd4079 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -81,6 +81,7 @@ lets you design your own [custom functions](custom-functions). | DELTA | Engineering | Returns TRUE (1) if both numbers are equal, otherwise returns FALSE (0). | DELTA(Number_1; Number_2) | | ERF | Engineering | Returns values of the Gaussian error integral. | ERF(Lower_Limit; Upper_Limit) | | ERFC | Engineering | Returns complementary values of the Gaussian error integral between x and infinity. | ERFC(Lower_Limit) | +| ISBINARY | Information | Returns TRUE if provided value is a valid binary number. | ISBINARY(Value) | | ISBLANK | Information | Returns TRUE if the reference to a cell is blank. | ISBLANK(Value) | | ISERR | Information | Returns TRUE if the value is error value except #N/A!. | ISERR(Value) | | ISERROR | Information | Returns TRUE if the value is general error value. | ISERROR(Value) | diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index 14afe9818d..94316f8537 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -77,6 +77,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEX', INT: 'CELÁ.ČÁST', IPMT: 'PLATBA.ÚROK', + ISBINARY: 'ISBINARY', ISBLANK: 'JE.PRÁZDNÉ', ISERR: 'JE.CHYBA', ISERROR: 'JE.CHYBHODN', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 00516ebb17..7ca27a951d 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -77,6 +77,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEKS', INT: 'HELTAL', IPMT: 'R.YDELSE', + ISBINARY: 'ISBINARY', ISBLANK: 'ER.TOM', ISERR: 'ER.FJL', ISERROR: 'ER.FEJL', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index b876702876..38d97dc319 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -77,6 +77,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEX', INT: 'GANZZAHL', IPMT: 'ZINSZ', + ISBINARY: 'ISBINARY', ISBLANK: 'ISTLEER', ISERR: 'ISTFEHL', ISERROR: 'ISTFEHLER', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index 23bc9db587..8c10eedbe8 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -77,6 +77,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEX', INT: 'INT', IPMT: 'IPMT', + ISBINARY: 'ISBINARY', ISBLANK: 'ISBLANK', ISERR: 'ISERR', ISERROR: 'ISERROR', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index d533cdff7f..183b913b10 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -77,6 +77,7 @@ export const dictionary: RawTranslationPackage = { INDEX: 'INDICE', INT: 'ENTERO', IPMT: 'PAGOINT', + ISBINARY: 'ISBINARY', ISBLANK: 'ESBLANCO', ISERR: 'ESERR', ISERROR: 'ESERROR', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 9cdb6c9283..c2ceefc3f2 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -77,6 +77,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEKSI', INT: 'KOKONAISLUKU', IPMT: 'IPMT', + ISBINARY: 'ISBINARY', ISBLANK: 'ONTYHJÄ', ISERR: 'ONVIRH', ISERROR: 'ONVIRHE', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index 7126818b8d..e6fa7b17a8 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -77,6 +77,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEX', INT: 'ENT', IPMT: 'INTPER', + ISBINARY: 'ISBINARY', ISBLANK: 'ESTVIDE', ISERR: 'ESTERR', ISERROR: 'ESTERREUR', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index de18289d59..3e86a27c2e 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -77,6 +77,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEX', INT: 'INT', IPMT: 'RRÉSZLET', + ISBINARY: 'ISBINARY', ISBLANK: 'ÜRES', ISERR: 'HIBA.E', ISERROR: 'HIBÁS', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index 9c6313078d..a883ac5a17 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -77,6 +77,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDICE', INT: 'INT', IPMT: 'INTERESSI', + ISBINARY: 'ISBINARY', ISBLANK: 'VAL.VUOTO', ISERR: 'VAL.ERR', ISERROR: 'VAL.ERRORE', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index bea65ec68a..20bd3e345f 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -77,6 +77,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEKS', INT: 'HELTALL', IPMT: 'RAVDRAG', + ISBINARY: 'ISBINARY', ISBLANK: 'ERTOM', ISERR: 'ERF', ISERROR: 'ERFEIL', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index 05d2b7c451..9ccfc8b237 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -77,6 +77,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEX', INT: 'INTEGER', IPMT: 'IBET', + ISBINARY: 'ISBINARY', ISBLANK: 'ISLEEG', ISERR: 'ISFOUT2', ISERROR: 'ISFOUT', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index fb9c40b00e..5016104c70 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -77,6 +77,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEKS', INT: 'ZAOKR.DO.CAŁK', IPMT: 'IPMT', + ISBINARY: 'ISBINARY', ISBLANK: 'CZY.PUSTA', ISERR: 'CZY.BŁ', ISERROR: 'CZY.BŁĄD', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index e4b8bd383f..58aa5f7f7b 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -77,6 +77,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'ÍNDICE', INT: 'INT', IPMT: 'IPGTO', + ISBINARY: 'ISBINARY', ISBLANK: 'ÉCÉL.VAZIA', ISERR: 'ÉERRO', ISERROR: 'ÉERROS', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index a977117c39..3eb70bf07c 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -77,6 +77,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'ИНДЕКС', INT: 'ЦЕЛОЕ', IPMT: 'ПРПЛТ', + ISBINARY: 'ISBINARY', ISBLANK: 'ЕПУСТО', ISERR: 'ЕОШ', ISERROR: 'ЕОШИБКА', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index 62fcc8ab2d..f56b7ea9d5 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -77,6 +77,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEX', INT: 'HELTAL', IPMT: 'RBETALNING', + ISBINARY: 'ISBINARY', ISBLANK: 'ÄRTOM', ISERR: 'ÄRF', ISERROR: 'ÄRFEL', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index ca77c02c71..5f1172c2ad 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -77,6 +77,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'İNDİS', INT: 'TAMSAYI', IPMT: 'FAİZTUTARI', + ISBINARY: 'ISBINARY', ISBLANK: 'EBOŞSA', ISERR: 'EHATA', ISERROR: 'EHATALIYSA', diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index b40594342b..199b924eff 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -20,6 +20,12 @@ export class InformationPlugin extends FunctionPlugin { isDependentOnSheetStructureChange: true, doesNotNeedArgumentsToBeComputed: true, }, + 'ISBINARY': { + method: 'isbinary', + parameters: [ + {argumentType: 'string'} + ] + }, 'ISBLANK': { method: 'isblank', parameters: { @@ -123,6 +129,20 @@ export class InformationPlugin extends FunctionPlugin { } } + /** + * Corresponds to ISBINARY(value) + * + * Returns true if provided value is a valid binary number + * + * @param ast + * @param formulaAddress + */ + public isbinary(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISBINARY.parameters, (arg: string) => + /^[01]{1,10}$/.test(arg) + ) + } + /** * Corresponds to ISERR(value) * diff --git a/test/interpreter/function-isbinary.spec.ts b/test/interpreter/function-isbinary.spec.ts new file mode 100644 index 0000000000..178fd5d2c8 --- /dev/null +++ b/test/interpreter/function-isbinary.spec.ts @@ -0,0 +1,24 @@ +import {HyperFormula} from '../../src' +import {adr} from '../testUtils' + +describe('Function ISBINARY', () => { + it('should return true for binary numbers', () => { + const engine = HyperFormula.buildFromArray([ + ['=ISBINARY("1010")', '=ISBINARY(1001)', '=ISBINARY(010)'] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(true) + expect(engine.getCellValue(adr('B1'))).toEqual(true) + expect(engine.getCellValue(adr('C1'))).toEqual(true) + }) + + it('should return false otherwise', () => { + const engine = HyperFormula.buildFromArray([ + ['=ISBINARY("foo")', '=ISBINARY(123)', '=ISBINARY(TRUE())'] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(false) + expect(engine.getCellValue(adr('B1'))).toEqual(false) + expect(engine.getCellValue(adr('C1'))).toEqual(false) + }) +}) \ No newline at end of file From 2cbdf81adc9fe8763ee01932eb4501436d0ada05 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Sat, 25 Jul 2020 09:53:30 +0200 Subject: [PATCH 80/97] Reference argument template --- src/interpreter/plugin/InformationPlugin.ts | 104 ++++++++++---------- 1 file changed, 53 insertions(+), 51 deletions(-) diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index 199b924eff..2ca5060f09 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -5,9 +5,9 @@ import {AbsoluteCellRange} from '../../AbsoluteCellRange' import {CellError, EmptyValue, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' -import {AstNodeType, ProcedureAst} from '../../parser' +import {Ast, AstNodeType, ProcedureAst} from '../../parser' import {InterpreterValue} from '../InterpreterValue' -import {FunctionPlugin} from './FunctionPlugin' +import {FunctionArgumentsDefinition, FunctionPlugin} from './FunctionPlugin' import {Maybe} from '../../Maybe' /** @@ -22,9 +22,11 @@ export class InformationPlugin extends FunctionPlugin { }, 'ISBINARY': { method: 'isbinary', - parameters: [ - {argumentType: 'string'} - ] + parameters: { + list: [ + {argumentType: 'string'} + ] + } }, 'ISBLANK': { method: 'isblank', @@ -138,7 +140,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public isbinary(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithDefaults(ast.args, formulaAddress, InformationPlugin.implementedFunctions.ISBINARY.parameters, (arg: string) => + return this.runFunction(ast.args, formulaAddress, this.parameters('ISBINARY'), (arg: string) => /^[01]{1,10}$/.test(arg) ) } @@ -379,38 +381,18 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress * */ public sheet(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length > 1) { - return new CellError(ErrorType.NA) - } else if (ast.args.length == 0) { - return formulaAddress.sheet + 1 - } - const arg = ast.args[0] - - let cellReference: Maybe - - if (arg.type === AstNodeType.CELL_REFERENCE) { - cellReference = arg.reference.toSimpleCellAddress(formulaAddress) - } else if (arg.type === AstNodeType.CELL_RANGE || arg.type === AstNodeType.COLUMN_RANGE || arg.type === AstNodeType.ROW_RANGE) { - try { - cellReference = AbsoluteCellRange.fromAst(arg, formulaAddress).start - } catch (e) { - return new CellError(ErrorType.REF) - } - } - - if (cellReference !== undefined) { - return cellReference.sheet + 1 - } - - /* Not using static parameters definition as we expect only values coerced to string from now on. */ - return this.runFunction(ast.args, formulaAddress, {list: [{argumentType: 'string'}]}, (value: string) => { - const sheetNumber = this.dependencyGraph.sheetMapping.get(value) - if (sheetNumber !== undefined) { - return sheetNumber + 1 - } else { - return new CellError(ErrorType.NA) + return this.runFunctionWithReferenceArgument(ast.args, formulaAddress, { list: [{argumentType: 'string'}] }, + () => formulaAddress.sheet + 1, + (reference: SimpleCellAddress) => reference.sheet + 1, + (value: string) => { + const sheetNumber = this.dependencyGraph.sheetMapping.get(value) + if (sheetNumber !== undefined) { + return sheetNumber + 1 + } else { + return new CellError(ErrorType.NA) + } } - }) + ) } /** @@ -423,24 +405,44 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress * */ public sheets(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length > 1) { + return this.runFunctionWithReferenceArgument(ast.args, formulaAddress, { list: [{argumentType: 'string'}] }, + () => this.dependencyGraph.sheetMapping.numberOfSheets(), // return number of sheets if no argument + () => 1, // return 1 for valid reference + () => new CellError(ErrorType.VALUE) // error otherwise + ) + } + + protected runFunctionWithReferenceArgument = ( + args: Ast[], + formulaAddress: SimpleCellAddress, + argumentDefinitions: FunctionArgumentsDefinition, + noArgCallback: () => InternalScalarValue, + referenceCallback: (reference: SimpleCellAddress) => InternalScalarValue, + nonReferenceCallback: (...arg: any) => InternalScalarValue + ) => { + if (args.length === 0) { + return noArgCallback() + } else if (args.length > 1) { return new CellError(ErrorType.NA) - } else if (ast.args.length == 0) { - return this.dependencyGraph.sheetMapping.numberOfSheets() + } + const arg = args[0] + + let cellReference: Maybe + + if (arg.type === AstNodeType.CELL_REFERENCE) { + cellReference = arg.reference.toSimpleCellAddress(formulaAddress) + } else if (arg.type === AstNodeType.CELL_RANGE || arg.type === AstNodeType.COLUMN_RANGE || arg.type === AstNodeType.ROW_RANGE) { + try { + cellReference = AbsoluteCellRange.fromAst(arg, formulaAddress).start + } catch (e) { + return new CellError(ErrorType.REF) + } } - const arg = ast.args[0] - if ( - arg.type === AstNodeType.CELL_REFERENCE || - arg.type === AstNodeType.CELL_RANGE || - arg.type === AstNodeType.COLUMN_RANGE || - arg.type === AstNodeType.ROW_RANGE - ) { - return 1 + if (cellReference !== undefined) { + return referenceCallback(cellReference) } - return this.runFunction(ast.args, formulaAddress, this.parameters('SHEETS'), () => { - return new CellError(ErrorType.VALUE) - }) + return this.runFunction(args, formulaAddress, argumentDefinitions, nonReferenceCallback) } } From 0ad3bc5b26a9c4afdaf44210e0e59ea6f4a8e672 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Sat, 25 Jul 2020 10:43:04 +0200 Subject: [PATCH 81/97] Function ISFORMULA --- docs/guide/built-in-functions.md | 1 + src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + src/interpreter/plugin/InformationPlugin.ts | 32 +++++++++++++- test/interpreter/function-isformula.spec.ts | 49 +++++++++++++++++++++ 19 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 test/interpreter/function-isformula.spec.ts diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index 912afd4079..437ee8bf43 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -86,6 +86,7 @@ lets you design your own [custom functions](custom-functions). | ISERR | Information | Returns TRUE if the value is error value except #N/A!. | ISERR(Value) | | ISERROR | Information | Returns TRUE if the value is general error value. | ISERROR(Value) | | ISEVEN | Information | Returns TRUE if the value is an even integer, or FALSE if the value is odd. | ISEVEN(Value) | +| ISFORMULA | Information | Checks whether referenced cell is a formula. | ISFORMULA(Value) | | ISLOGICAL | Information | Tests for a logical value (TRUE or FALSE). | ISLOGICAL(Value) | | ISNA | Information | Returns TRUE if the value is #N/A! error. | ISNA(Value) | | ISNONTEXT | Information | Tests if the cell contents are text or numbers, and returns FALSE if the contents are text. | ISNONTEXT(Value) | diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index 94316f8537..68f8a25812 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERR: 'JE.CHYBA', ISERROR: 'JE.CHYBHODN', ISEVEN: 'ISEVEN', + ISFORMULA: 'ISFORMULA', ISLOGICAL: 'JE.LOGHODN', ISNA: 'JE.NEDEF', ISNONTEXT: 'JE.NETEXT', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 7ca27a951d..d1d824b767 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERR: 'ER.FJL', ISERROR: 'ER.FEJL', ISEVEN: 'ER.LIGE', + ISFORMULA: 'ER.FORMEL', ISLOGICAL: 'ER.LOGISK', ISNA: 'ER.IKKE.TILGÆNGELIG', ISNONTEXT: 'ER.IKKE.TEKST', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index 38d97dc319..3de8ef765d 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERR: 'ISTFEHL', ISERROR: 'ISTFEHLER', ISEVEN: 'ISTGERADE', + ISFORMULA: 'ISTFORMEL', ISLOGICAL: 'ISTLOG', ISNA: 'ISTNV', ISNONTEXT: 'ISTKTEXT', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index 8c10eedbe8..4765c7b808 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERR: 'ISERR', ISERROR: 'ISERROR', ISEVEN: 'ISEVEN', + ISFORMULA: 'ISFORMULA', ISLOGICAL: 'ISLOGICAL', ISNA: 'ISNA', ISNONTEXT: 'ISNONTEXT', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index 183b913b10..0f48e7b96e 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -82,6 +82,7 @@ export const dictionary: RawTranslationPackage = { ISERR: 'ESERR', ISERROR: 'ESERROR', ISEVEN: 'ES.PAR', + ISFORMULA: 'ISFORMULA', ISLOGICAL: 'ESLOGICO', ISNA: 'ESNOD', ISNONTEXT: 'ESNOTEXTO', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index c2ceefc3f2..dcb54d139a 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERR: 'ONVIRH', ISERROR: 'ONVIRHE', ISEVEN: 'ONPARILLINEN', + ISFORMULA: 'ONKAAVA', ISLOGICAL: 'ONTOTUUS', ISNA: 'ONPUUTTUU', ISNONTEXT: 'ONEI_TEKSTI', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index e6fa7b17a8..7993c4a2e4 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERR: 'ESTERR', ISERROR: 'ESTERREUR', ISEVEN: 'EST.PAIR', + ISFORMULA: 'ESTFORMULE', ISLOGICAL: 'ESTLOGIQUE', ISNA: 'ESTNA', ISNONTEXT: 'ESTNONTEXTE', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index 3e86a27c2e..9cac059907 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERR: 'HIBA.E', ISERROR: 'HIBÁS', ISEVEN: 'PÁROSE', + ISFORMULA: 'KÉPLET', ISLOGICAL: 'LOGIKAI', ISNA: 'NINCS', ISNONTEXT: 'NEM.SZÖVEG', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index a883ac5a17..c5b3581b66 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERR: 'VAL.ERR', ISERROR: 'VAL.ERRORE', ISEVEN: 'VAL.PARI', + ISFORMULA: 'VAL.FORMULA', ISLOGICAL: 'VAL.LOGICO', ISNA: 'VAL.NON.DISP', ISNONTEXT: 'VAL.NON.TESTO', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index 20bd3e345f..17def8c862 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERR: 'ERF', ISERROR: 'ERFEIL', ISEVEN: 'ERPARTALL', + ISFORMULA: 'ERFORMEL', ISLOGICAL: 'ERLOGISK', ISNA: 'ERIT', ISNONTEXT: 'ERIKKETEKST', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index 9ccfc8b237..4011ec59a3 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERR: 'ISFOUT2', ISERROR: 'ISFOUT', ISEVEN: 'IS.EVEN', + ISFORMULA: 'ISFORMULE', ISLOGICAL: 'ISLOGISCH', ISNA: 'ISNB', ISNONTEXT: 'ISGEENTEKST', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index 5016104c70..ca2620e801 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERR: 'CZY.BŁ', ISERROR: 'CZY.BŁĄD', ISEVEN: 'CZY.PARZYSTE', + ISFORMULA: 'CZY.FORMUŁA', ISLOGICAL: 'CZY.LOGICZNA', ISNA: 'CZY.BRAK', ISNONTEXT: 'CZY.NIE.TEKST', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index 58aa5f7f7b..14bcf08282 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERR: 'ÉERRO', ISERROR: 'ÉERROS', ISEVEN: 'ÉPAR', + ISFORMULA: 'ÉFÓRMULA', ISLOGICAL: 'ÉLÓGICO', ISNA: 'É.NÃO.DISP', ISNONTEXT: 'É.NÃO.TEXTO', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index 3eb70bf07c..9fc40077f7 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERR: 'ЕОШ', ISERROR: 'ЕОШИБКА', ISEVEN: 'ЕЧЁТН', + ISFORMULA: 'ЕФОРМУЛА', ISLOGICAL: 'ЕЛОГИЧ', ISNA: 'ЕНД', ISNONTEXT: 'ЕНЕТЕКСТ', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index f56b7ea9d5..0772955356 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERR: 'ÄRF', ISERROR: 'ÄRFEL', ISEVEN: 'ÄRJÄMN', + ISFORMULA: 'ISFORMEL', ISLOGICAL: 'ÄRLOGISK', ISNA: 'ÄRSAKNAD', ISNONTEXT: 'ÄREJTEXT', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index 5f1172c2ad..52ba9e478b 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -82,6 +82,7 @@ const dictionary: RawTranslationPackage = { ISERR: 'EHATA', ISERROR: 'EHATALIYSA', ISEVEN: 'ÇİFTMİ', + ISFORMULA: 'EFORMÜLSE', ISLOGICAL: 'EMANTIKSALSA', ISNA: 'EYOKSA', ISNONTEXT: 'EMETİNDEĞİLSE', diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index 2ca5060f09..c84f79ccd7 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -9,6 +9,7 @@ import {Ast, AstNodeType, ProcedureAst} from '../../parser' import {InterpreterValue} from '../InterpreterValue' import {FunctionArgumentsDefinition, FunctionPlugin} from './FunctionPlugin' import {Maybe} from '../../Maybe' +import {FormulaCellVertex, MatrixVertex} from '../../DependencyGraph' /** * Interpreter plugin containing information functions @@ -52,6 +53,14 @@ export class InformationPlugin extends FunctionPlugin { ] } }, + 'ISFORMULA': { + method: 'isformula', + parameters: { + list: [ + {argumentType: 'noerror'} + ] + } + }, 'ISNA': { method: 'isna', parameters: { @@ -173,6 +182,25 @@ export class InformationPlugin extends FunctionPlugin { ) } + /** + * Corresponds to ISFORMULA(value) + * + * Checks whether referenced cell is a formula + * + * @param ast + * @param formulaAddress + */ + public isformula(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { + return this.runFunctionWithReferenceArgument(ast.args, formulaAddress, this.parameters('ISFORMULA'), + () => new CellError(ErrorType.NA), + (reference: SimpleCellAddress) => { + const vertex = this.dependencyGraph.addressMapping.getCell(reference) + return vertex instanceof FormulaCellVertex || (vertex instanceof MatrixVertex && vertex.isFormula()) + }, + () => new CellError(ErrorType.NA) + ) + } + /** * Corresponds to ISBLANK(value) * @@ -381,7 +409,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress * */ public sheet(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithReferenceArgument(ast.args, formulaAddress, { list: [{argumentType: 'string'}] }, + return this.runFunctionWithReferenceArgument(ast.args, formulaAddress, {list: [{argumentType: 'string'}]}, () => formulaAddress.sheet + 1, (reference: SimpleCellAddress) => reference.sheet + 1, (value: string) => { @@ -405,7 +433,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress * */ public sheets(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithReferenceArgument(ast.args, formulaAddress, { list: [{argumentType: 'string'}] }, + return this.runFunctionWithReferenceArgument(ast.args, formulaAddress, {list: [{argumentType: 'string'}]}, () => this.dependencyGraph.sheetMapping.numberOfSheets(), // return number of sheets if no argument () => 1, // return 1 for valid reference () => new CellError(ErrorType.VALUE) // error otherwise diff --git a/test/interpreter/function-isformula.spec.ts b/test/interpreter/function-isformula.spec.ts new file mode 100644 index 0000000000..5508ad01e0 --- /dev/null +++ b/test/interpreter/function-isformula.spec.ts @@ -0,0 +1,49 @@ +import {ErrorType, HyperFormula} from '../../src' +import {adr, detailedError} from '../testUtils' + +describe('Function ISFORMULA', () => { + it('should return true for cell with formula', () => { + const engine = HyperFormula.buildFromArray([ + ['=A1', '=ISFORMULA(A1)'] + ]) + + expect(engine.getCellValue(adr('B1'))).toEqual(true) + }) + + it('should return false for cell without formula', () => { + const engine = HyperFormula.buildFromArray([ + ['foo', '=ISFORMULA(A1)', '=ISFORMULA(A2)'] + ]) + + expect(engine.getCellValue(adr('B1'))).toEqual(false) + expect(engine.getCellValue(adr('C1'))).toEqual(false) + }) + + it('should work with start of a range', () => { + const engine = HyperFormula.buildFromArray([ + ['=A1', 2, '=ISFORMULA(A1:A2)', '=ISFORMULA(B1:B2)'] + ]) + + expect(engine.getCellValue(adr('C1'))).toEqual(true) + expect(engine.getCellValue(adr('D1'))).toEqual(false) + }) + + it('should propagate error', () => { + const engine = HyperFormula.buildFromArray([ + ['=ISFORMULA(1/0)'] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.DIV_BY_ZERO)) + }) + + it('should return NA otherwise', () => { + const engine = HyperFormula.buildFromArray([ + ['=ISFORMULA()', '=ISFORMULA(A1, A2)', '=ISFORMULA("foo")', '=ISFORMULA(42)'] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NA)) + expect(engine.getCellValue(adr('B1'))).toEqual(detailedError(ErrorType.NA)) + expect(engine.getCellValue(adr('C1'))).toEqual(detailedError(ErrorType.NA)) + expect(engine.getCellValue(adr('D1'))).toEqual(detailedError(ErrorType.NA)) + }) +}) \ No newline at end of file From b29c173d889f33bf825c6f33138b535da64c7323 Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Sat, 25 Jul 2020 10:50:55 +0200 Subject: [PATCH 82/97] Function ISFORMULA should be able to check itself --- src/interpreter/plugin/InformationPlugin.ts | 3 ++- test/interpreter/function-isformula.spec.ts | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index c84f79ccd7..7560e4d43a 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -59,7 +59,8 @@ export class InformationPlugin extends FunctionPlugin { list: [ {argumentType: 'noerror'} ] - } + }, + doesNotNeedArgumentsToBeComputed: true }, 'ISNA': { method: 'isna', diff --git a/test/interpreter/function-isformula.spec.ts b/test/interpreter/function-isformula.spec.ts index 5508ad01e0..17730fc999 100644 --- a/test/interpreter/function-isformula.spec.ts +++ b/test/interpreter/function-isformula.spec.ts @@ -46,4 +46,12 @@ describe('Function ISFORMULA', () => { expect(engine.getCellValue(adr('C1'))).toEqual(detailedError(ErrorType.NA)) expect(engine.getCellValue(adr('D1'))).toEqual(detailedError(ErrorType.NA)) }) + + it('should work for itself', () => { + const engine = HyperFormula.buildFromArray([ + ['=ISFORMULA(A1)'] + ]) + + expect(engine.getCellValue(adr('A1'))).toEqual(true) + }) }) \ No newline at end of file From d494685f8d569bd7ff136284ff5c84cfcee7899c Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Thu, 30 Jul 2020 17:46:26 +0200 Subject: [PATCH 83/97] Refactor FORMULATEXT --- src/interpreter/plugin/FormulaTextPlugin.ts | 50 ++++++++------------- src/interpreter/plugin/FunctionPlugin.ts | 36 ++++++++++++++- src/interpreter/plugin/InformationPlugin.ts | 34 -------------- src/interpreter/plugin/index.ts | 2 +- 4 files changed, 55 insertions(+), 67 deletions(-) diff --git a/src/interpreter/plugin/FormulaTextPlugin.ts b/src/interpreter/plugin/FormulaTextPlugin.ts index 3d6fe35301..9c53141bb6 100644 --- a/src/interpreter/plugin/FormulaTextPlugin.ts +++ b/src/interpreter/plugin/FormulaTextPlugin.ts @@ -3,49 +3,37 @@ * Copyright (c) 2020 Handsoncode. All rights reserved. */ -import {AstNodeType, ProcedureAst} from '../../parser' +import {ProcedureAst} from '../../parser' import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {FunctionPlugin} from '../index' -import {AbsoluteCellRange} from '../../AbsoluteCellRange' -import {Maybe} from '../../Maybe' export class FormulaTextPlugin extends FunctionPlugin { public static implementedFunctions = { 'FORMULATEXT': { method: 'formulatext', + parameters: { + list: [ + {argumentType: 'noerror'} + ] + }, doesNotNeedArgumentsToBeComputed: true, isDependentOnSheetStructureChange: true }, } + /** + * Corresponds to FORMULATEXT(value) + * + * Returns a formula in a given cell as a string. + * + * @param ast + * @param formulaAddress + * */ public formulatext(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - if (ast.args.length !== 1) { - return new CellError(ErrorType.NA) - } - - const arg = ast.args[0] - - let cellReference: Maybe - - if (arg.type === AstNodeType.CELL_REFERENCE) { - cellReference = arg.reference.toSimpleCellAddress(formulaAddress) - } else if (arg.type === AstNodeType.CELL_RANGE || arg.type === AstNodeType.COLUMN_RANGE || arg.type === AstNodeType.ROW_RANGE) { - try { - cellReference = AbsoluteCellRange.fromAst(arg, formulaAddress).start - } catch (e) { - return new CellError(ErrorType.REF) - } - } - - if (cellReference === undefined) { - const value = this.evaluateAst(arg, formulaAddress) - if (value instanceof CellError) { - return value - } - return new CellError(ErrorType.NA) - } - - const formula = this.serialization.getCellFormula(cellReference) - return formula || new CellError(ErrorType.NA) + return this.runFunctionWithReferenceArgument(ast.args, formulaAddress, this.parameters('FORMULATEXT'), + () => new CellError(ErrorType.NA), + (cellReference: SimpleCellAddress) => this.serialization.getCellFormula(cellReference) || new CellError(ErrorType.NA), + () => new CellError(ErrorType.NA) + ) } } \ No newline at end of file diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 14235e3558..555e7adc12 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -9,7 +9,7 @@ import {ColumnSearchStrategy} from '../../ColumnSearch/ColumnSearchStrategy' import {Config} from '../../Config' import {DependencyGraph} from '../../DependencyGraph' import {Maybe} from '../../Maybe' -import {Ast, ProcedureAst} from '../../parser' +import {Ast, AstNodeType, ProcedureAst} from '../../parser' import {coerceScalarToBoolean, coerceScalarToString} from '../ArithmeticHelper' import {Interpreter} from '../Interpreter' import {InterpreterValue, SimpleRangeValue} from '../InterpreterValue' @@ -207,6 +207,40 @@ export abstract class FunctionPlugin { return argCoerceFailure ?? fn(...coercedArguments) } + protected runFunctionWithReferenceArgument = ( + args: Ast[], + formulaAddress: SimpleCellAddress, + argumentDefinitions: FunctionArgumentsDefinition, + noArgCallback: () => InternalScalarValue, + referenceCallback: (reference: SimpleCellAddress) => InternalScalarValue, + nonReferenceCallback: (...arg: any) => InternalScalarValue + ) => { + if (args.length === 0) { + return noArgCallback() + } else if (args.length > 1) { + return new CellError(ErrorType.NA) + } + const arg = args[0] + + let cellReference: Maybe + + if (arg.type === AstNodeType.CELL_REFERENCE) { + cellReference = arg.reference.toSimpleCellAddress(formulaAddress) + } else if (arg.type === AstNodeType.CELL_RANGE || arg.type === AstNodeType.COLUMN_RANGE || arg.type === AstNodeType.ROW_RANGE) { + try { + cellReference = AbsoluteCellRange.fromAst(arg, formulaAddress).start + } catch (e) { + return new CellError(ErrorType.REF) + } + } + + if (cellReference !== undefined) { + return referenceCallback(cellReference) + } + + return this.runFunction(args, formulaAddress, argumentDefinitions, nonReferenceCallback) + } + protected parameters(name: string): FunctionArgumentsDefinition { const params = (this.constructor as FunctionPluginDefinition).implementedFunctions[name]?.parameters if (params !== undefined) { diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index 7560e4d43a..958284379e 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -440,38 +440,4 @@ export class InformationPlugin extends FunctionPlugin { () => new CellError(ErrorType.VALUE) // error otherwise ) } - - protected runFunctionWithReferenceArgument = ( - args: Ast[], - formulaAddress: SimpleCellAddress, - argumentDefinitions: FunctionArgumentsDefinition, - noArgCallback: () => InternalScalarValue, - referenceCallback: (reference: SimpleCellAddress) => InternalScalarValue, - nonReferenceCallback: (...arg: any) => InternalScalarValue - ) => { - if (args.length === 0) { - return noArgCallback() - } else if (args.length > 1) { - return new CellError(ErrorType.NA) - } - const arg = args[0] - - let cellReference: Maybe - - if (arg.type === AstNodeType.CELL_REFERENCE) { - cellReference = arg.reference.toSimpleCellAddress(formulaAddress) - } else if (arg.type === AstNodeType.CELL_RANGE || arg.type === AstNodeType.COLUMN_RANGE || arg.type === AstNodeType.ROW_RANGE) { - try { - cellReference = AbsoluteCellRange.fromAst(arg, formulaAddress).start - } catch (e) { - return new CellError(ErrorType.REF) - } - } - - if (cellReference !== undefined) { - return referenceCallback(cellReference) - } - - return this.runFunction(args, formulaAddress, argumentDefinitions, nonReferenceCallback) - } } diff --git a/src/interpreter/plugin/index.ts b/src/interpreter/plugin/index.ts index 6d84e997a1..b26299efb9 100644 --- a/src/interpreter/plugin/index.ts +++ b/src/interpreter/plugin/index.ts @@ -37,4 +37,4 @@ export {CharPlugin} from './CharPlugin' export {CodePlugin} from './CodePlugin' export {ErrorFunctionPlugin} from './ErrorFunctionPlugin' export {CorrelPlugin} from './CorrelPlugin' -export {FormulaTextPlugin} from './FormulaTextPlugin' \ No newline at end of file +export {FormulaTextPlugin} from './FormulaTextPlugin' From ae1e08fd8f4948250bd70bb8f2f98410c372acad Mon Sep 17 00:00:00 2001 From: Jakub Kowalski Date: Thu, 30 Jul 2020 17:46:56 +0200 Subject: [PATCH 84/97] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a8a2879d1..5dc9324425 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added helper methods for keeping track of cell/range dependencies: getCellPrecedents and getCellDependents. (#441) - Added 4 financial functions FV, PMT, PPMT, IPMT. - Added FORMULATEXT function. +- Added 8 information functions ISERR, ISNA, ISREF, NA, SHEET, SHEETS, ISBINARY, ISFORMULA ### Changed - Operation `moveCells` creating cyclic dependencies does not cause losing original formula. (#479) From 481f1da415e1538379b0175cdc37e4c69e60131a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Uzna=C5=84ski?= <40573492+izulin@users.noreply.github.com> Date: Thu, 30 Jul 2020 22:31:16 +0200 Subject: [PATCH 85/97] Update CHANGELOG.md Co-authored-by: Wojciech Czerniak --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a8a2879d1..c30eef4e3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Operation `moveCells` creating cyclic dependencies does not cause losing original formula. (#479) -- Simplified adding new function modules, reworked (simplified) implementations of existing modules. +- Simplified adding new function modules, reworked (simplified) implementations of existing modules. (#480) ### Fixed - Fixed hardcoding of languages in i18n tests. (#471) From 90ee8a0a270bc44f9330f1667270a9a6ca898eed Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 30 Jul 2020 23:34:13 +0200 Subject: [PATCH 86/97] enum --- src/interpreter/plugin/AbsPlugin.ts | 4 +- src/interpreter/plugin/BitShiftPlugin.ts | 10 ++-- .../plugin/BitwiseLogicOperationsPlugin.ts | 14 +++--- src/interpreter/plugin/BooleanPlugin.ts | 34 +++++++------- src/interpreter/plugin/CharPlugin.ts | 4 +- src/interpreter/plugin/CodePlugin.ts | 4 +- src/interpreter/plugin/CountUniquePlugin.ts | 4 +- src/interpreter/plugin/DatePlugin.ts | 26 +++++------ src/interpreter/plugin/DegreesPlugin.ts | 4 +- src/interpreter/plugin/DeltaPlugin.ts | 6 +-- src/interpreter/plugin/ErrorFunctionPlugin.ts | 8 ++-- src/interpreter/plugin/ExpPlugin.ts | 4 +- src/interpreter/plugin/FinancialPlugin.ts | 46 +++++++++---------- src/interpreter/plugin/FunctionPlugin.ts | 34 ++++++++------ src/interpreter/plugin/InformationPlugin.ts | 14 +++--- src/interpreter/plugin/IsEvenPlugin.ts | 4 +- src/interpreter/plugin/IsOddPlugin.ts | 4 +- src/interpreter/plugin/LogarithmPlugin.ts | 10 ++-- src/interpreter/plugin/MedianPlugin.ts | 4 +- src/interpreter/plugin/ModuloPlugin.ts | 6 +-- src/interpreter/plugin/PowerPlugin.ts | 6 +-- src/interpreter/plugin/RadiansPlugin.ts | 4 +- .../plugin/RadixConversionPlugin.ts | 34 +++++++------- src/interpreter/plugin/RoundingPlugin.ts | 30 ++++++------ src/interpreter/plugin/SqrtPlugin.ts | 4 +- src/interpreter/plugin/TextPlugin.ts | 40 ++++++++-------- src/interpreter/plugin/TrigonometryPlugin.ts | 20 ++++---- test/optional-parameters.spec.ts | 12 ++--- 28 files changed, 200 insertions(+), 194 deletions(-) diff --git a/src/interpreter/plugin/AbsPlugin.ts b/src/interpreter/plugin/AbsPlugin.ts index 078353f23c..527e3a3e3d 100644 --- a/src/interpreter/plugin/AbsPlugin.ts +++ b/src/interpreter/plugin/AbsPlugin.ts @@ -5,14 +5,14 @@ import {InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' export class AbsPlugin extends FunctionPlugin { public static implementedFunctions = { 'ABS': { method: 'abs', parameters: { list: [ - { argumentType: 'number' } + { argumentType: ArgumentTypes.NUMBER } ]} }, } diff --git a/src/interpreter/plugin/BitShiftPlugin.ts b/src/interpreter/plugin/BitShiftPlugin.ts index 135bd8d23f..8629180b24 100644 --- a/src/interpreter/plugin/BitShiftPlugin.ts +++ b/src/interpreter/plugin/BitShiftPlugin.ts @@ -5,7 +5,7 @@ import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' const MAX_48BIT_INTEGER = 281474976710655 const SHIFT_MIN_POSITIONS = -53 @@ -16,15 +16,15 @@ export class BitShiftPlugin extends FunctionPlugin { 'BITLSHIFT': { method: 'bitlshift', parameters: { list: [ - { argumentType: 'integer', minValue: 0 }, - { argumentType: 'integer', minValue: SHIFT_MIN_POSITIONS, maxValue: SHIFT_MAX_POSITIONS }, + { argumentType: ArgumentTypes.INTEGER, minValue: 0 }, + { argumentType: ArgumentTypes.INTEGER, minValue: SHIFT_MIN_POSITIONS, maxValue: SHIFT_MAX_POSITIONS }, ]} }, 'BITRSHIFT': { method: 'bitrshift', parameters: { list: [ - { argumentType: 'integer', minValue: 0 }, - { argumentType: 'integer', minValue: SHIFT_MIN_POSITIONS, maxValue: SHIFT_MAX_POSITIONS }, + { argumentType: ArgumentTypes.INTEGER, minValue: 0 }, + { argumentType: ArgumentTypes.INTEGER, minValue: SHIFT_MIN_POSITIONS, maxValue: SHIFT_MAX_POSITIONS }, ]} }, } diff --git a/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts b/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts index f3c04fbfe3..2c5ff5e654 100644 --- a/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts +++ b/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts @@ -5,29 +5,29 @@ import {InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' export class BitwiseLogicOperationsPlugin extends FunctionPlugin { public static implementedFunctions = { 'BITAND': { method: 'bitand', parameters: { list: [ - { argumentType: 'integer', minValue: 0 }, - { argumentType: 'integer', minValue: 0 }, + { argumentType: ArgumentTypes.INTEGER, minValue: 0 }, + { argumentType: ArgumentTypes.INTEGER, minValue: 0 }, ]} }, 'BITOR': { method: 'bitor', parameters: { list: [ - { argumentType: 'integer', minValue: 0 }, - { argumentType: 'integer', minValue: 0 }, + { argumentType: ArgumentTypes.INTEGER, minValue: 0 }, + { argumentType: ArgumentTypes.INTEGER, minValue: 0 }, ]} }, 'BITXOR': { method: 'bitxor', parameters: { list: [ - { argumentType: 'integer', minValue: 0 }, - { argumentType: 'integer', minValue: 0 }, + { argumentType: ArgumentTypes.INTEGER, minValue: 0 }, + { argumentType: ArgumentTypes.INTEGER, minValue: 0 }, ]} }, } diff --git a/src/interpreter/plugin/BooleanPlugin.ts b/src/interpreter/plugin/BooleanPlugin.ts index 6ed398342c..a78f5eb6b4 100644 --- a/src/interpreter/plugin/BooleanPlugin.ts +++ b/src/interpreter/plugin/BooleanPlugin.ts @@ -6,7 +6,7 @@ import {CellError, ErrorType, InternalNoErrorCellValue, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' import {InterpreterValue} from '../InterpreterValue' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' /** * Interpreter plugin containing boolean functions @@ -25,9 +25,9 @@ export class BooleanPlugin extends FunctionPlugin { method: 'conditionalIf', parameters: { list: [ - {argumentType: 'boolean'}, - {argumentType: 'scalar'}, - {argumentType: 'scalar', defaultValue: false}, + {argumentType: ArgumentTypes.BOOLEAN}, + {argumentType: ArgumentTypes.SCALAR}, + {argumentType: ArgumentTypes.SCALAR, defaultValue: false}, ] }, }, @@ -35,7 +35,7 @@ export class BooleanPlugin extends FunctionPlugin { method: 'and', parameters: { list: [ - {argumentType: 'boolean'}, + {argumentType: ArgumentTypes.BOOLEAN}, ], repeatedArg: true, expandRanges: true, @@ -45,7 +45,7 @@ export class BooleanPlugin extends FunctionPlugin { method: 'or', parameters: { list: [ - {argumentType: 'boolean'}, + {argumentType: ArgumentTypes.BOOLEAN}, ], repeatedArg: true, expandRanges: true, @@ -55,7 +55,7 @@ export class BooleanPlugin extends FunctionPlugin { method: 'xor', parameters: { list: [ - {argumentType: 'boolean'}, + {argumentType: ArgumentTypes.BOOLEAN}, ], repeatedArg: true, expandRanges: true, @@ -65,7 +65,7 @@ export class BooleanPlugin extends FunctionPlugin { method: 'not', parameters: { list: [ - {argumentType: 'boolean'}, + {argumentType: ArgumentTypes.BOOLEAN}, ] }, }, @@ -73,9 +73,9 @@ export class BooleanPlugin extends FunctionPlugin { method: 'switch', parameters: { list: [ - {argumentType: 'noerror'}, - {argumentType: 'scalar'}, - {argumentType: 'scalar'}, + {argumentType: ArgumentTypes.NOERROR}, + {argumentType: ArgumentTypes.SCALAR}, + {argumentType: ArgumentTypes.SCALAR}, ], repeatedArg: true, }, @@ -84,8 +84,8 @@ export class BooleanPlugin extends FunctionPlugin { method: 'iferror', parameters: { list: [ - {argumentType: 'scalar'}, - {argumentType: 'scalar'}, + {argumentType: ArgumentTypes.SCALAR}, + {argumentType: ArgumentTypes.SCALAR}, ] }, }, @@ -93,8 +93,8 @@ export class BooleanPlugin extends FunctionPlugin { method: 'ifna', parameters: { list: [ - {argumentType: 'scalar'}, - {argumentType: 'scalar'}, + {argumentType: ArgumentTypes.SCALAR}, + {argumentType: ArgumentTypes.SCALAR}, ] }, }, @@ -102,8 +102,8 @@ export class BooleanPlugin extends FunctionPlugin { method: 'choose', parameters: { list: [ - {argumentType: 'number'}, - {argumentType: 'scalar'}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.SCALAR}, ], repeatedArg: true, }, diff --git a/src/interpreter/plugin/CharPlugin.ts b/src/interpreter/plugin/CharPlugin.ts index 3e4f318307..ddf44af14e 100644 --- a/src/interpreter/plugin/CharPlugin.ts +++ b/src/interpreter/plugin/CharPlugin.ts @@ -5,7 +5,7 @@ import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' export class CharPlugin extends FunctionPlugin { public static implementedFunctions = { @@ -13,7 +13,7 @@ export class CharPlugin extends FunctionPlugin { method: 'char', parameters: { list: [ - {argumentType: 'number'} + {argumentType: ArgumentTypes.NUMBER} ], } }, diff --git a/src/interpreter/plugin/CodePlugin.ts b/src/interpreter/plugin/CodePlugin.ts index 62ecfa1023..3c39809f06 100644 --- a/src/interpreter/plugin/CodePlugin.ts +++ b/src/interpreter/plugin/CodePlugin.ts @@ -5,7 +5,7 @@ import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' export class CodePlugin extends FunctionPlugin { public static implementedFunctions = { @@ -13,7 +13,7 @@ export class CodePlugin extends FunctionPlugin { method: 'code', parameters: { list: [ - {argumentType: 'string'} + {argumentType: ArgumentTypes.STRING} ] }, }, diff --git a/src/interpreter/plugin/CountUniquePlugin.ts b/src/interpreter/plugin/CountUniquePlugin.ts index e20965f3eb..4d469ed26b 100644 --- a/src/interpreter/plugin/CountUniquePlugin.ts +++ b/src/interpreter/plugin/CountUniquePlugin.ts @@ -5,7 +5,7 @@ import {CellError, EmptyValueType, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' /** * Interpreter plugin containing COUNTUNIQUE function @@ -16,7 +16,7 @@ export class CountUniquePlugin extends FunctionPlugin { method: 'countunique', parameters: { list: [ - {argumentType: 'scalar'}, + {argumentType: ArgumentTypes.SCALAR}, ], repeatedArg: true, expandRanges: true, diff --git a/src/interpreter/plugin/DatePlugin.ts b/src/interpreter/plugin/DatePlugin.ts index ef17248886..6db1939e3d 100644 --- a/src/interpreter/plugin/DatePlugin.ts +++ b/src/interpreter/plugin/DatePlugin.ts @@ -8,7 +8,7 @@ import {endOfMonth, offsetMonth} from '../../DateTimeHelper' import {format} from '../../format/format' import {AstNodeType, ProcedureAst} from '../../parser' import {SimpleRangeValue} from '../InterpreterValue' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' /** * Interpreter plugin containing date-specific functions @@ -19,9 +19,9 @@ export class DatePlugin extends FunctionPlugin { method: 'date', parameters: { list: [ - {argumentType: 'number'}, - {argumentType: 'number'}, - {argumentType: 'number'}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER}, ] }, }, @@ -29,7 +29,7 @@ export class DatePlugin extends FunctionPlugin { method: 'month', parameters: { list: [ - {argumentType: 'number'}, + {argumentType: ArgumentTypes.NUMBER}, ] }, }, @@ -37,7 +37,7 @@ export class DatePlugin extends FunctionPlugin { method: 'year', parameters: { list: [ - {argumentType: 'number'}, + {argumentType: ArgumentTypes.NUMBER}, ] }, }, @@ -45,8 +45,8 @@ export class DatePlugin extends FunctionPlugin { method: 'text', parameters: { list: [ - {argumentType: 'number'}, - {argumentType: 'string'}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.STRING}, ] }, }, @@ -54,8 +54,8 @@ export class DatePlugin extends FunctionPlugin { method: 'eomonth', parameters: { list: [ - {argumentType: 'number'}, - {argumentType: 'number'}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER}, ] }, }, @@ -63,7 +63,7 @@ export class DatePlugin extends FunctionPlugin { method: 'day', parameters: { list: [ - {argumentType: 'number'}, + {argumentType: ArgumentTypes.NUMBER}, ] }, }, @@ -71,8 +71,8 @@ export class DatePlugin extends FunctionPlugin { method: 'days', parameters: { list: [ - {argumentType: 'number'}, - {argumentType: 'number'}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER}, ] }, }, diff --git a/src/interpreter/plugin/DegreesPlugin.ts b/src/interpreter/plugin/DegreesPlugin.ts index 05c82c4de5..b1e428b366 100644 --- a/src/interpreter/plugin/DegreesPlugin.ts +++ b/src/interpreter/plugin/DegreesPlugin.ts @@ -5,7 +5,7 @@ import {InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' export class DegreesPlugin extends FunctionPlugin { public static implementedFunctions = { @@ -13,7 +13,7 @@ export class DegreesPlugin extends FunctionPlugin { method: 'degrees', parameters: { list: [ - {argumentType: 'number'} + {argumentType: ArgumentTypes.NUMBER} ] }, }, diff --git a/src/interpreter/plugin/DeltaPlugin.ts b/src/interpreter/plugin/DeltaPlugin.ts index 5ecba964a5..96bd936da0 100644 --- a/src/interpreter/plugin/DeltaPlugin.ts +++ b/src/interpreter/plugin/DeltaPlugin.ts @@ -5,7 +5,7 @@ import {InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' export class DeltaPlugin extends FunctionPlugin { public static implementedFunctions = { @@ -13,8 +13,8 @@ export class DeltaPlugin extends FunctionPlugin { method: 'delta', parameters: { list: [ - {argumentType: 'number'}, - {argumentType: 'number', defaultValue: 0}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, ] }, }, diff --git a/src/interpreter/plugin/ErrorFunctionPlugin.ts b/src/interpreter/plugin/ErrorFunctionPlugin.ts index d5930243ad..f4cfa362f1 100644 --- a/src/interpreter/plugin/ErrorFunctionPlugin.ts +++ b/src/interpreter/plugin/ErrorFunctionPlugin.ts @@ -5,7 +5,7 @@ import {InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' export class ErrorFunctionPlugin extends FunctionPlugin { public static implementedFunctions = { @@ -13,8 +13,8 @@ export class ErrorFunctionPlugin extends FunctionPlugin { method: 'erf', parameters: { list: [ - {argumentType: 'number'}, - {argumentType: 'number', optionalArg: true}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER, optionalArg: true}, ] }, }, @@ -22,7 +22,7 @@ export class ErrorFunctionPlugin extends FunctionPlugin { method: 'erfc', parameters: { list: [ - {argumentType: 'number'} + {argumentType: ArgumentTypes.NUMBER} ] }, }, diff --git a/src/interpreter/plugin/ExpPlugin.ts b/src/interpreter/plugin/ExpPlugin.ts index 7350cbfbc5..cfe3a97e9a 100644 --- a/src/interpreter/plugin/ExpPlugin.ts +++ b/src/interpreter/plugin/ExpPlugin.ts @@ -5,14 +5,14 @@ import {InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' export class ExpPlugin extends FunctionPlugin { public static implementedFunctions = { 'EXP': { method: 'exp', parameters: { list: [ - { argumentType: 'number' } + { argumentType: ArgumentTypes.NUMBER } ]}, }, } diff --git a/src/interpreter/plugin/FinancialPlugin.ts b/src/interpreter/plugin/FinancialPlugin.ts index 5f98e39f0f..c3e033e554 100644 --- a/src/interpreter/plugin/FinancialPlugin.ts +++ b/src/interpreter/plugin/FinancialPlugin.ts @@ -5,7 +5,7 @@ import {InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' export class FinancialPlugin extends FunctionPlugin { public static implementedFunctions = { @@ -13,11 +13,11 @@ export class FinancialPlugin extends FunctionPlugin { method: 'pmt', parameters: { list: [ - {argumentType: 'number'}, - {argumentType: 'number'}, - {argumentType: 'number'}, - {argumentType: 'number', defaultValue: 0}, - {argumentType: 'number', defaultValue: 0}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, ] }, }, @@ -25,12 +25,12 @@ export class FinancialPlugin extends FunctionPlugin { method: 'ipmt', parameters: { list: [ - {argumentType: 'number'}, - {argumentType: 'number'}, - {argumentType: 'number'}, - {argumentType: 'number'}, - {argumentType: 'number', defaultValue: 0}, - {argumentType: 'number', defaultValue: 0}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, ] }, }, @@ -38,12 +38,12 @@ export class FinancialPlugin extends FunctionPlugin { method: 'ppmt', parameters: { list: [ - {argumentType: 'number'}, - {argumentType: 'number'}, - {argumentType: 'number'}, - {argumentType: 'number'}, - {argumentType: 'number', defaultValue: 0}, - {argumentType: 'number', defaultValue: 0}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, ] }, }, @@ -51,11 +51,11 @@ export class FinancialPlugin extends FunctionPlugin { method: 'fv', parameters: { list: [ - {argumentType: 'number'}, - {argumentType: 'number'}, - {argumentType: 'number'}, - {argumentType: 'number', defaultValue: 0}, - {argumentType: 'number', defaultValue: 0}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, ] }, }, diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 14235e3558..737b1f995f 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -33,7 +33,15 @@ export interface FunctionPluginDefinition { implementedFunctions: ImplementedFunctions, } -export type ArgumentTypes = 'string' | 'number' | 'boolean' | 'scalar' | 'noerror' | 'range' | 'integer' +export enum ArgumentTypes { + STRING = 'STRING', + NUMBER = 'NUMBER', + BOOLEAN = 'BOOLEAN', + SCALAR = 'scalar', + NOERROR = 'noerror', + RANGE = 'range', + INTEGER = 'integer', +} export interface FunctionArgumentsDefinition { list: FunctionArgument[], @@ -42,7 +50,7 @@ export interface FunctionArgumentsDefinition { } export interface FunctionArgument { - argumentType: string, + argumentType: ArgumentTypes, defaultValue?: InternalScalarValue, optionalArg?: boolean, minValue?: number, @@ -110,15 +118,15 @@ export abstract class FunctionPlugin { public coerceToType(arg: InterpreterValue, coercedType: FunctionArgument): Maybe { if(arg instanceof SimpleRangeValue) { - if(coercedType.argumentType === 'range') { + if(coercedType.argumentType === ArgumentTypes.RANGE) { return arg } else { return undefined } } else { - switch (coercedType.argumentType as ArgumentTypes) { - case 'integer': - case 'number': + switch (coercedType.argumentType) { + case ArgumentTypes.INTEGER: + case ArgumentTypes.NUMBER: // eslint-disable-next-line no-case-declarations const value = this.coerceScalarToNumberOrError(arg) if(typeof value !== 'number') { @@ -136,19 +144,19 @@ export abstract class FunctionPlugin { if (coercedType.greaterThan !== undefined && value <= coercedType.greaterThan) { return new CellError(ErrorType.NUM) } - if(coercedType.argumentType === 'integer' && !Number.isInteger(value)) { + if(coercedType.argumentType === ArgumentTypes.INTEGER && !Number.isInteger(value)) { return new CellError(ErrorType.NUM) } return value - case 'string': + case ArgumentTypes.STRING: return coerceScalarToString(arg) - case 'boolean': + case ArgumentTypes.BOOLEAN: return coerceScalarToBoolean(arg) - case 'scalar': + case ArgumentTypes.SCALAR: return arg - case 'noerror': + case ArgumentTypes.NOERROR: return arg - case 'range': + case ArgumentTypes.RANGE: return undefined } } @@ -192,7 +200,7 @@ export abstract class FunctionPlugin { //we apply coerce only to non-default values const coercedArg = val !== undefined ? this.coerceToType(arg, argumentDefinitions[j]) : arg if(coercedArg !== undefined) { - if (coercedArg instanceof CellError && argumentDefinitions[j].argumentType !== 'scalar') { + if (coercedArg instanceof CellError && argumentDefinitions[j].argumentType !== ArgumentTypes.SCALAR) { //if this is first error encountered, store it argCoerceFailure = argCoerceFailure ?? coercedArg } diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index ff5f4a1049..b4deaa3f9b 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -7,7 +7,7 @@ import {AbsoluteCellRange} from '../../AbsoluteCellRange' import {CellError, EmptyValue, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {AstNodeType, ProcedureAst} from '../../parser' import {InterpreterValue} from '../InterpreterValue' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' /** * Interpreter plugin containing information functions @@ -17,37 +17,37 @@ export class InformationPlugin extends FunctionPlugin { 'ISERROR': { method: 'iserror', parameters: { list: [ - { argumentType: 'scalar'} + { argumentType: ArgumentTypes.SCALAR} ]} }, 'ISBLANK': { method: 'isblank', parameters: { list: [ - { argumentType: 'scalar'} + { argumentType: ArgumentTypes.SCALAR} ]} }, 'ISNUMBER': { method: 'isnumber', parameters: { list: [ - { argumentType: 'scalar'} + { argumentType: ArgumentTypes.SCALAR} ]} }, 'ISLOGICAL': { method: 'islogical', parameters: { list: [ - { argumentType: 'scalar'} + { argumentType: ArgumentTypes.SCALAR} ]} }, 'ISTEXT': { method: 'istext', parameters: { list: [ - { argumentType: 'scalar'} + { argumentType: ArgumentTypes.SCALAR} ]} }, 'ISNONTEXT': { method: 'isnontext', parameters: { list: [ - { argumentType: 'scalar'} + { argumentType: ArgumentTypes.SCALAR} ]} }, 'COLUMNS': { diff --git a/src/interpreter/plugin/IsEvenPlugin.ts b/src/interpreter/plugin/IsEvenPlugin.ts index f8154d7b4e..67a3432d49 100644 --- a/src/interpreter/plugin/IsEvenPlugin.ts +++ b/src/interpreter/plugin/IsEvenPlugin.ts @@ -5,14 +5,14 @@ import {InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' export class IsEvenPlugin extends FunctionPlugin { public static implementedFunctions = { 'ISEVEN': { method: 'iseven', parameters: { list: [ - { argumentType: 'number'} + { argumentType: ArgumentTypes.NUMBER} ]} }, } diff --git a/src/interpreter/plugin/IsOddPlugin.ts b/src/interpreter/plugin/IsOddPlugin.ts index 9398305409..3e307575b3 100644 --- a/src/interpreter/plugin/IsOddPlugin.ts +++ b/src/interpreter/plugin/IsOddPlugin.ts @@ -5,7 +5,7 @@ import {InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' export class IsOddPlugin extends FunctionPlugin { public static implementedFunctions = { @@ -13,7 +13,7 @@ export class IsOddPlugin extends FunctionPlugin { method: 'isodd', parameters: { list: [ - {argumentType: 'number'} + {argumentType: ArgumentTypes.NUMBER} ] } }, diff --git a/src/interpreter/plugin/LogarithmPlugin.ts b/src/interpreter/plugin/LogarithmPlugin.ts index dafbbacd87..8346b4a22d 100644 --- a/src/interpreter/plugin/LogarithmPlugin.ts +++ b/src/interpreter/plugin/LogarithmPlugin.ts @@ -5,7 +5,7 @@ import {InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' export class LogarithmPlugin extends FunctionPlugin { @@ -14,7 +14,7 @@ export class LogarithmPlugin extends FunctionPlugin { method: 'log10', parameters: { list: [ - {argumentType: 'number'} + {argumentType: ArgumentTypes.NUMBER} ] }, }, @@ -22,8 +22,8 @@ export class LogarithmPlugin extends FunctionPlugin { method: 'log', parameters: { list: [ - {argumentType: 'number', greaterThan: 0}, - {argumentType: 'number', defaultValue: 10, greaterThan: 0}, + {argumentType: ArgumentTypes.NUMBER, greaterThan: 0}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 10, greaterThan: 0}, ] }, }, @@ -31,7 +31,7 @@ export class LogarithmPlugin extends FunctionPlugin { method: 'ln', parameters: { list: [ - {argumentType: 'number'} + {argumentType: ArgumentTypes.NUMBER} ] }, }, diff --git a/src/interpreter/plugin/MedianPlugin.ts b/src/interpreter/plugin/MedianPlugin.ts index 4163ba09aa..9c43727e5b 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 {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' /** * Interpreter plugin containing MEDIAN function @@ -17,7 +17,7 @@ export class MedianPlugin extends FunctionPlugin { method: 'median', parameters: { list: [ - {argumentType: 'noerror'}, + {argumentType: ArgumentTypes.NOERROR}, ], repeatedArg: true, expandRanges: true, diff --git a/src/interpreter/plugin/ModuloPlugin.ts b/src/interpreter/plugin/ModuloPlugin.ts index cae4bcdbcd..e91a1ebc69 100644 --- a/src/interpreter/plugin/ModuloPlugin.ts +++ b/src/interpreter/plugin/ModuloPlugin.ts @@ -5,15 +5,15 @@ import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' export class ModuloPlugin extends FunctionPlugin { public static implementedFunctions = { 'MOD': { method: 'mod', parameters: { list: [ - { argumentType: 'number' }, - { argumentType: 'number' }, + { argumentType: ArgumentTypes.NUMBER }, + { argumentType: ArgumentTypes.NUMBER }, ]}, }, } diff --git a/src/interpreter/plugin/PowerPlugin.ts b/src/interpreter/plugin/PowerPlugin.ts index 59ed57f72e..6d5b4cf5a9 100644 --- a/src/interpreter/plugin/PowerPlugin.ts +++ b/src/interpreter/plugin/PowerPlugin.ts @@ -5,15 +5,15 @@ import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' export class PowerPlugin extends FunctionPlugin { public static implementedFunctions = { 'POWER': { method: 'power', parameters: { list: [ - { argumentType: 'number' }, - { argumentType: 'number' }, + { argumentType: ArgumentTypes.NUMBER }, + { argumentType: ArgumentTypes.NUMBER }, ]}, }, } diff --git a/src/interpreter/plugin/RadiansPlugin.ts b/src/interpreter/plugin/RadiansPlugin.ts index ac0af90e0d..ed12a09c84 100644 --- a/src/interpreter/plugin/RadiansPlugin.ts +++ b/src/interpreter/plugin/RadiansPlugin.ts @@ -5,14 +5,14 @@ import {InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' export class RadiansPlugin extends FunctionPlugin { public static implementedFunctions = { 'RADIANS': { method: 'radians', parameters: { list: [ - { argumentType: 'number' } + { argumentType: ArgumentTypes.NUMBER } ]}, }, } diff --git a/src/interpreter/plugin/RadixConversionPlugin.ts b/src/interpreter/plugin/RadixConversionPlugin.ts index 2bb0235298..ea6b9891b1 100644 --- a/src/interpreter/plugin/RadixConversionPlugin.ts +++ b/src/interpreter/plugin/RadixConversionPlugin.ts @@ -7,7 +7,7 @@ import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../. import {padLeft} from '../../format/format' import {Maybe} from '../../Maybe' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' const NUMBER_OF_BITS = 10 const DECIMAL_NUMBER_OF_BITS = 255 @@ -20,57 +20,57 @@ export class RadixConversionPlugin extends FunctionPlugin { 'DEC2BIN': { method: 'dec2bin', parameters: { list: [ - { argumentType: 'number', minValue: minValFromBase(2), maxValue: maxValFromBase(2) }, - { argumentType: 'number', optionalArg: true, minValue: 1, maxValue: 10 }, + { argumentType: ArgumentTypes.NUMBER, minValue: minValFromBase(2), maxValue: maxValFromBase(2) }, + { argumentType: ArgumentTypes.NUMBER, optionalArg: true, minValue: 1, maxValue: 10 }, ]}, }, 'DEC2OCT': { method: 'dec2oct', parameters: { list: [ - { argumentType: 'number', minValue: minValFromBase(8), maxValue: maxValFromBase(8) }, - { argumentType: 'number', optionalArg: true, minValue: 1, maxValue: 10 }, + { argumentType: ArgumentTypes.NUMBER, minValue: minValFromBase(8), maxValue: maxValFromBase(8) }, + { argumentType: ArgumentTypes.NUMBER, optionalArg: true, minValue: 1, maxValue: 10 }, ]}, }, 'DEC2HEX': { method: 'dec2hex', parameters: { list: [ - { argumentType: 'number', minValue: minValFromBase(16), maxValue: maxValFromBase(16) }, - { argumentType: 'number', optionalArg: true, minValue: 1, maxValue: 10 }, + { argumentType: ArgumentTypes.NUMBER, minValue: minValFromBase(16), maxValue: maxValFromBase(16) }, + { argumentType: ArgumentTypes.NUMBER, optionalArg: true, minValue: 1, maxValue: 10 }, ]}, }, 'BIN2DEC': { method: 'bin2dec', parameters: { list: [ - { argumentType: 'string' } + { argumentType: ArgumentTypes.STRING } ]}, }, 'BIN2OCT': { method: 'bin2oct', parameters: { list: [ - { argumentType: 'string' }, - { argumentType: 'number', optionalArg: true, minValue: 0, maxValue: DECIMAL_NUMBER_OF_BITS}, + { argumentType: ArgumentTypes.STRING }, + { argumentType: ArgumentTypes.NUMBER, optionalArg: true, minValue: 0, maxValue: DECIMAL_NUMBER_OF_BITS}, ]}, }, 'BIN2HEX': { method: 'bin2hex', parameters: { list: [ - { argumentType: 'string' }, - { argumentType: 'number', optionalArg: true, minValue: 0, maxValue: DECIMAL_NUMBER_OF_BITS}, + { argumentType: ArgumentTypes.STRING }, + { argumentType: ArgumentTypes.NUMBER, optionalArg: true, minValue: 0, maxValue: DECIMAL_NUMBER_OF_BITS}, ]}, }, 'DECIMAL': { method: 'decimal', parameters: { list: [ - { argumentType: 'string' }, - { argumentType: 'number', minValue: MIN_BASE, maxValue: MAX_BASE }, + { argumentType: ArgumentTypes.STRING }, + { argumentType: ArgumentTypes.NUMBER, minValue: MIN_BASE, maxValue: MAX_BASE }, ]}, }, 'BASE': { method: 'base', parameters: { list: [ - { argumentType: 'number', minValue: 0 }, - { argumentType: 'number', minValue: MIN_BASE, maxValue: MAX_BASE }, - { argumentType: 'number', optionalArg: true, minValue: 0, maxValue: DECIMAL_NUMBER_OF_BITS}, + { argumentType: ArgumentTypes.NUMBER, minValue: 0 }, + { argumentType: ArgumentTypes.NUMBER, minValue: MIN_BASE, maxValue: MAX_BASE }, + { argumentType: ArgumentTypes.NUMBER, optionalArg: true, minValue: 0, maxValue: DECIMAL_NUMBER_OF_BITS}, ]}, }, } diff --git a/src/interpreter/plugin/RoundingPlugin.ts b/src/interpreter/plugin/RoundingPlugin.ts index 6ffa0c2444..d60e040538 100644 --- a/src/interpreter/plugin/RoundingPlugin.ts +++ b/src/interpreter/plugin/RoundingPlugin.ts @@ -5,7 +5,7 @@ import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' export function findNextOddNumber(arg: number): number { const ceiled = Math.ceil(arg) @@ -22,55 +22,55 @@ export class RoundingPlugin extends FunctionPlugin { 'ROUNDUP': { method: 'roundup', parameters: { list: [ - { argumentType: 'number' }, - { argumentType: 'number', defaultValue: 0}, + { argumentType: ArgumentTypes.NUMBER }, + { argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, ]}, }, 'ROUNDDOWN': { method: 'rounddown', parameters: { list: [ - { argumentType: 'number' }, - { argumentType: 'number', defaultValue: 0}, + { argumentType: ArgumentTypes.NUMBER }, + { argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, ]}, }, 'ROUND': { method: 'round', parameters: { list: [ - { argumentType: 'number' }, - { argumentType: 'number', defaultValue: 0}, + { argumentType: ArgumentTypes.NUMBER }, + { argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, ]}, }, 'TRUNC': { method: 'trunc', parameters: { list: [ - { argumentType: 'number' }, - { argumentType: 'number', defaultValue: 0}, + { argumentType: ArgumentTypes.NUMBER }, + { argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, ]}, }, 'INT': { method: 'intFunc', parameters: { list: [ - { argumentType: 'number' } + { argumentType: ArgumentTypes.NUMBER } ]}, }, 'EVEN': { method: 'even', parameters: { list: [ - { argumentType: 'number' } + { argumentType: ArgumentTypes.NUMBER } ]}, }, 'ODD': { method: 'odd', parameters: { list: [ - { argumentType: 'number' } + { argumentType: ArgumentTypes.NUMBER } ]}, }, 'CEILING': { method: 'ceiling', parameters: { list: [ - { argumentType: 'number' }, - { argumentType: 'number', defaultValue: 1 }, - { argumentType: 'number', defaultValue: 0 }, + { argumentType: ArgumentTypes.NUMBER }, + { argumentType: ArgumentTypes.NUMBER, defaultValue: 1 }, + { argumentType: ArgumentTypes.NUMBER, defaultValue: 0 }, ]}, }, } diff --git a/src/interpreter/plugin/SqrtPlugin.ts b/src/interpreter/plugin/SqrtPlugin.ts index f1b0174de2..ab3ca3b438 100644 --- a/src/interpreter/plugin/SqrtPlugin.ts +++ b/src/interpreter/plugin/SqrtPlugin.ts @@ -5,14 +5,14 @@ import {InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' export class SqrtPlugin extends FunctionPlugin { public static implementedFunctions = { 'SQRT': { method: 'sqrt', parameters: { list: [ - { argumentType: 'number' } + { argumentType: ArgumentTypes.NUMBER } ]}, }, } diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index 4e758b5ba6..551cf1d0a1 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -5,7 +5,7 @@ import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' /** * Interpreter plugin containing text-specific functions @@ -16,7 +16,7 @@ export class TextPlugin extends FunctionPlugin { method: 'concatenate', parameters: { list: [ - {argumentType: 'string'} + {argumentType: ArgumentTypes.STRING} ], repeatedArg: true, expandRanges: true, @@ -26,8 +26,8 @@ export class TextPlugin extends FunctionPlugin { method: 'split', parameters: { list: [ - {argumentType: 'string'}, - {argumentType: 'number'}, + {argumentType: ArgumentTypes.STRING}, + {argumentType: ArgumentTypes.NUMBER}, ] }, }, @@ -35,7 +35,7 @@ export class TextPlugin extends FunctionPlugin { method: 'len', parameters: { list: [ - {argumentType: 'string'} + {argumentType: ArgumentTypes.STRING} ] }, }, @@ -43,7 +43,7 @@ export class TextPlugin extends FunctionPlugin { method: 'trim', parameters: { list: [ - {argumentType: 'string'} + {argumentType: ArgumentTypes.STRING} ] }, }, @@ -51,7 +51,7 @@ export class TextPlugin extends FunctionPlugin { method: 'proper', parameters: { list: [ - {argumentType: 'string'} + {argumentType: ArgumentTypes.STRING} ] }, }, @@ -59,7 +59,7 @@ export class TextPlugin extends FunctionPlugin { method: 'clean', parameters: { list: [ - {argumentType: 'string'} + {argumentType: ArgumentTypes.STRING} ] }, }, @@ -67,8 +67,8 @@ export class TextPlugin extends FunctionPlugin { method: 'rept', parameters: { list: [ - {argumentType: 'string'}, - {argumentType: 'number'}, + {argumentType: ArgumentTypes.STRING}, + {argumentType: ArgumentTypes.NUMBER}, ] }, }, @@ -76,8 +76,8 @@ export class TextPlugin extends FunctionPlugin { method: 'right', parameters: { list: [ - {argumentType: 'string'}, - {argumentType: 'number', defaultValue: 1}, + {argumentType: ArgumentTypes.STRING}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 1}, ] }, }, @@ -85,8 +85,8 @@ export class TextPlugin extends FunctionPlugin { method: 'left', parameters: { list: [ - {argumentType: 'string'}, - {argumentType: 'number', defaultValue: 1}, + {argumentType: ArgumentTypes.STRING}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 1}, ] }, }, @@ -94,9 +94,9 @@ export class TextPlugin extends FunctionPlugin { method: 'search', parameters: { list: [ - {argumentType: 'string'}, - {argumentType: 'string'}, - {argumentType: 'number', defaultValue: 1}, + {argumentType: ArgumentTypes.STRING}, + {argumentType: ArgumentTypes.STRING}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 1}, ] }, }, @@ -104,9 +104,9 @@ export class TextPlugin extends FunctionPlugin { method: 'find', parameters: { list: [ - {argumentType: 'string'}, - {argumentType: 'string'}, - {argumentType: 'number', defaultValue: 1}, + {argumentType: ArgumentTypes.STRING}, + {argumentType: ArgumentTypes.STRING}, + {argumentType: ArgumentTypes.NUMBER, defaultValue: 1}, ] }, } diff --git a/src/interpreter/plugin/TrigonometryPlugin.ts b/src/interpreter/plugin/TrigonometryPlugin.ts index e3615c4db7..dd17e22f2f 100644 --- a/src/interpreter/plugin/TrigonometryPlugin.ts +++ b/src/interpreter/plugin/TrigonometryPlugin.ts @@ -5,7 +5,7 @@ import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {ProcedureAst} from '../../parser' -import {FunctionPlugin} from './FunctionPlugin' +import {ArgumentTypes, FunctionPlugin} from './FunctionPlugin' /** * Interpreter plugin containing trigonometric functions @@ -16,50 +16,50 @@ export class TrigonometryPlugin extends FunctionPlugin { 'ACOS': { method: 'acos', parameters: { list: [ - { argumentType: 'number' } + { argumentType: ArgumentTypes.NUMBER } ]} }, 'ASIN': { method: 'asin', parameters: { list: [ - { argumentType: 'number' } + { argumentType: ArgumentTypes.NUMBER } ]} }, 'COS': { method: 'cos', parameters: { list: [ - { argumentType: 'number' } + { argumentType: ArgumentTypes.NUMBER } ]} }, 'SIN': { method: 'sin', parameters: { list: [ - { argumentType: 'number' } + { argumentType: ArgumentTypes.NUMBER } ]} }, 'TAN': { method: 'tan', parameters: { list: [ - { argumentType: 'number' } + { argumentType: ArgumentTypes.NUMBER } ]} }, 'ATAN': { method: 'atan', parameters: { list: [ - { argumentType: 'number' } + { argumentType: ArgumentTypes.NUMBER } ]} }, 'ATAN2': { method: 'atan2', parameters: { list: [ - { argumentType: 'number' }, - { argumentType: 'number' }, + { argumentType: ArgumentTypes.NUMBER }, + { argumentType: ArgumentTypes.NUMBER }, ]} }, 'COT': { method: 'ctg', parameters: { list: [ - { argumentType: 'number' } + { argumentType: ArgumentTypes.NUMBER } ]} }, } diff --git a/test/optional-parameters.spec.ts b/test/optional-parameters.spec.ts index b2d0800d15..ff4c05231d 100644 --- a/test/optional-parameters.spec.ts +++ b/test/optional-parameters.spec.ts @@ -1,9 +1,7 @@ import {ErrorType, HyperFormula} from '../src' -import {CellError, SimpleCellAddress} from '../src/Cell' -import {coerceScalarToString} from '../src/interpreter/ArithmeticHelper' -import {SimpleRangeValue} from '../src/interpreter/InterpreterValue' -import {FunctionPlugin} from '../src/interpreter/plugin/FunctionPlugin' -import {AstNodeType, ProcedureAst} from '../src/parser' +import {SimpleCellAddress} from '../src/Cell' +import {ArgumentTypes, FunctionPlugin} from '../src/interpreter/plugin/FunctionPlugin' +import {ProcedureAst} from '../src/parser' import {adr, detailedError} from './testUtils' class FooPlugin extends FunctionPlugin { @@ -12,8 +10,8 @@ class FooPlugin extends FunctionPlugin { method: 'foo', parameters: { list: [ - { argumentType: 'string', defaultValue: 'default1'}, - { argumentType: 'string', defaultValue: 'default2'}, + { argumentType: ArgumentTypes.STRING, defaultValue: 'default1'}, + { argumentType: ArgumentTypes.STRING, defaultValue: 'default2'}, ], }, }, From ac3d75847a5ae3f5cd6d837d3cbc34cca1114283 Mon Sep 17 00:00:00 2001 From: izulin Date: Thu, 30 Jul 2020 23:52:42 +0200 Subject: [PATCH 87/97] docs --- src/interpreter/plugin/BooleanPlugin.ts | 10 +-- src/interpreter/plugin/CountUniquePlugin.ts | 2 +- src/interpreter/plugin/FunctionPlugin.ts | 73 +++++++++++++++++++-- src/interpreter/plugin/MedianPlugin.ts | 2 +- src/interpreter/plugin/TextPlugin.ts | 2 +- 5 files changed, 75 insertions(+), 14 deletions(-) diff --git a/src/interpreter/plugin/BooleanPlugin.ts b/src/interpreter/plugin/BooleanPlugin.ts index a78f5eb6b4..761073036e 100644 --- a/src/interpreter/plugin/BooleanPlugin.ts +++ b/src/interpreter/plugin/BooleanPlugin.ts @@ -37,7 +37,7 @@ export class BooleanPlugin extends FunctionPlugin { list: [ {argumentType: ArgumentTypes.BOOLEAN}, ], - repeatedArg: true, + repeatLastArg: true, expandRanges: true, }, }, @@ -47,7 +47,7 @@ export class BooleanPlugin extends FunctionPlugin { list: [ {argumentType: ArgumentTypes.BOOLEAN}, ], - repeatedArg: true, + repeatLastArg: true, expandRanges: true, }, }, @@ -57,7 +57,7 @@ export class BooleanPlugin extends FunctionPlugin { list: [ {argumentType: ArgumentTypes.BOOLEAN}, ], - repeatedArg: true, + repeatLastArg: true, expandRanges: true, }, }, @@ -77,7 +77,7 @@ export class BooleanPlugin extends FunctionPlugin { {argumentType: ArgumentTypes.SCALAR}, {argumentType: ArgumentTypes.SCALAR}, ], - repeatedArg: true, + repeatLastArg: true, }, }, 'IFERROR': { @@ -105,7 +105,7 @@ export class BooleanPlugin extends FunctionPlugin { {argumentType: ArgumentTypes.NUMBER}, {argumentType: ArgumentTypes.SCALAR}, ], - repeatedArg: true, + repeatLastArg: true, }, }, } diff --git a/src/interpreter/plugin/CountUniquePlugin.ts b/src/interpreter/plugin/CountUniquePlugin.ts index 4d469ed26b..d3c14911ce 100644 --- a/src/interpreter/plugin/CountUniquePlugin.ts +++ b/src/interpreter/plugin/CountUniquePlugin.ts @@ -18,7 +18,7 @@ export class CountUniquePlugin extends FunctionPlugin { list: [ {argumentType: ArgumentTypes.SCALAR}, ], - repeatedArg: true, + repeatLastArg: true, expandRanges: true, }, }, diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 737b1f995f..9d5dd2f6c0 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -34,28 +34,89 @@ export interface FunctionPluginDefinition { } export enum ArgumentTypes { + + /** + * String type. + */ STRING = 'STRING', + + /** + * Floating point type. + */ NUMBER = 'NUMBER', + + /** + * Boolean type. + */ BOOLEAN = 'BOOLEAN', - SCALAR = 'scalar', - NOERROR = 'noerror', - RANGE = 'range', - INTEGER = 'integer', + + /** + * Any non-range value. + */ + SCALAR = 'SCALAR', + + /** + * Any non-range, no-error type. + */ + NOERROR = 'NOERROR', + + /** + * Range type. + */ + RANGE = 'RANGE', + + /** + * Integer type. + */ + INTEGER = 'INTEGER', } export interface FunctionArgumentsDefinition { list: FunctionArgument[], - repeatedArg?: boolean, + + /** + * Used for functions with variable number of arguments -- last defined argument is repeated indefinitely. + */ + repeatLastArg?: boolean, + + /** + * Ranges in arguments are inlined to (possibly multiple) scalar arguments. + */ expandRanges?: boolean, } export interface FunctionArgument { argumentType: ArgumentTypes, + + /** + * If argument is missing, its value defaults to this. + */ defaultValue?: InternalScalarValue, + + /** + * If argument is missing, and no defaultValue provided, undefined is supplied as a value, instead of throwing an error. + * Logically equivalent to setting defaultValue = undefined. + */ optionalArg?: boolean, + + /** + * Numeric argument needs to be greater-equal than this. + */ minValue?: number, + + /** + * Numeric argument needs to be less-equal than this. + */ maxValue?: number, + + /** + * Numeric argument needs to be less than this. + */ lessThan?: number, + + /** + * Numeric argument needs to be greater than this. + */ greaterThan?: number, } @@ -180,7 +241,7 @@ export abstract class FunctionPlugin { const coercedArguments: Maybe[] = [] let argCoerceFailure: Maybe = undefined - if(!functionDefinition.repeatedArg && argumentDefinitions.length < scalarValues.length) { + if(!functionDefinition.repeatLastArg && argumentDefinitions.length < scalarValues.length) { return new CellError(ErrorType.NA) } for(let i=0; i Date: Tue, 4 Aug 2020 13:37:00 +0200 Subject: [PATCH 88/97] flattening --- src/HyperFormula.ts | 4 +- src/interpreter/plugin/AbsPlugin.ts | 6 +- src/interpreter/plugin/BitShiftPlugin.ts | 12 ++-- .../plugin/BitwiseLogicOperationsPlugin.ts | 18 ++--- src/interpreter/plugin/BooleanPlugin.ts | 64 +++++++----------- src/interpreter/plugin/CharPlugin.ts | 6 +- src/interpreter/plugin/CodePlugin.ts | 6 +- src/interpreter/plugin/CountUniquePlugin.ts | 6 +- src/interpreter/plugin/DatePlugin.ts | 42 ++++-------- src/interpreter/plugin/DegreesPlugin.ts | 6 +- src/interpreter/plugin/DeltaPlugin.ts | 6 +- src/interpreter/plugin/ErrorFunctionPlugin.ts | 12 ++-- src/interpreter/plugin/ExpPlugin.ts | 6 +- src/interpreter/plugin/FinancialPlugin.ts | 24 +++---- src/interpreter/plugin/FunctionPlugin.ts | 34 +++++----- src/interpreter/plugin/InformationPlugin.ts | 36 +++++----- src/interpreter/plugin/IsEvenPlugin.ts | 6 +- src/interpreter/plugin/IsOddPlugin.ts | 6 +- src/interpreter/plugin/LogarithmPlugin.ts | 18 ++--- src/interpreter/plugin/MathConstantsPlugin.ts | 8 +-- src/interpreter/plugin/MedianPlugin.ts | 6 +- src/interpreter/plugin/ModuloPlugin.ts | 6 +- src/interpreter/plugin/PowerPlugin.ts | 6 +- src/interpreter/plugin/RadiansPlugin.ts | 6 +- .../plugin/RadixConversionPlugin.ts | 48 +++++++------- src/interpreter/plugin/RandomPlugin.ts | 4 +- src/interpreter/plugin/RoundingPlugin.ts | 46 ++++++------- src/interpreter/plugin/SqrtPlugin.ts | 6 +- src/interpreter/plugin/TextPlugin.ts | 66 +++++++------------ src/interpreter/plugin/TrigonometryPlugin.ts | 48 +++++++------- test/interpreter/function-vlookup.spec.ts | 4 +- test/optional-parameters.spec.ts | 8 +-- 32 files changed, 244 insertions(+), 336 deletions(-) diff --git a/src/HyperFormula.ts b/src/HyperFormula.ts index d82295f929..802ab23487 100644 --- a/src/HyperFormula.ts +++ b/src/HyperFormula.ts @@ -854,7 +854,7 @@ export class HyperFormula implements TypedEmitter { } /** - * Updates the config with given new parameters. + * Updates the config with given new metadata. * * @param {Partial} newParams configuration options to be updated or added * @@ -900,7 +900,7 @@ export class HyperFormula implements TypedEmitter { * * @example * ```js - * // should return all config parameters including default and those which were added + * // should return all config metadata including default and those which were added * const hfConfig = hfInstance.getConfig(); * ``` * diff --git a/src/interpreter/plugin/AbsPlugin.ts b/src/interpreter/plugin/AbsPlugin.ts index 527e3a3e3d..d72396768a 100644 --- a/src/interpreter/plugin/AbsPlugin.ts +++ b/src/interpreter/plugin/AbsPlugin.ts @@ -11,13 +11,13 @@ export class AbsPlugin extends FunctionPlugin { public static implementedFunctions = { 'ABS': { method: 'abs', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER } - ]} + ] }, } public abs(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('ABS'), Math.abs) + return this.runFunction(ast.args, formulaAddress, this.metadata('ABS'), Math.abs) } } diff --git a/src/interpreter/plugin/BitShiftPlugin.ts b/src/interpreter/plugin/BitShiftPlugin.ts index 8629180b24..09e0521d8a 100644 --- a/src/interpreter/plugin/BitShiftPlugin.ts +++ b/src/interpreter/plugin/BitShiftPlugin.ts @@ -15,26 +15,26 @@ export class BitShiftPlugin extends FunctionPlugin { public static implementedFunctions = { 'BITLSHIFT': { method: 'bitlshift', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.INTEGER, minValue: 0 }, { argumentType: ArgumentTypes.INTEGER, minValue: SHIFT_MIN_POSITIONS, maxValue: SHIFT_MAX_POSITIONS }, - ]} + ] }, 'BITRSHIFT': { method: 'bitrshift', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.INTEGER, minValue: 0 }, { argumentType: ArgumentTypes.INTEGER, minValue: SHIFT_MIN_POSITIONS, maxValue: SHIFT_MAX_POSITIONS }, - ]} + ] }, } public bitlshift(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('BITLSHIFT'), shiftLeft) + return this.runFunction(ast.args, formulaAddress, this.metadata('BITLSHIFT'), shiftLeft) } public bitrshift(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('BITRSHIFT'), shiftRight) + return this.runFunction(ast.args, formulaAddress, this.metadata('BITRSHIFT'), shiftRight) } } diff --git a/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts b/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts index 2c5ff5e654..135ba51f30 100644 --- a/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts +++ b/src/interpreter/plugin/BitwiseLogicOperationsPlugin.ts @@ -11,41 +11,41 @@ export class BitwiseLogicOperationsPlugin extends FunctionPlugin { public static implementedFunctions = { 'BITAND': { method: 'bitand', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.INTEGER, minValue: 0 }, { argumentType: ArgumentTypes.INTEGER, minValue: 0 }, - ]} + ] }, 'BITOR': { method: 'bitor', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.INTEGER, minValue: 0 }, { argumentType: ArgumentTypes.INTEGER, minValue: 0 }, - ]} + ] }, 'BITXOR': { method: 'bitxor', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.INTEGER, minValue: 0 }, { argumentType: ArgumentTypes.INTEGER, minValue: 0 }, - ]} + ] }, } public bitand(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('BITAND'), + return this.runFunction(ast.args, formulaAddress, this.metadata('BITAND'), (left: number, right: number) => left & right ) } public bitor(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('BITOR'), + return this.runFunction(ast.args, formulaAddress, this.metadata('BITOR'), (left: number, right: number) => left | right ) } public bitxor(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('BITXOR'), + return this.runFunction(ast.args, formulaAddress, this.metadata('BITXOR'), (left: number, right: number) => left ^ right ) } diff --git a/src/interpreter/plugin/BooleanPlugin.ts b/src/interpreter/plugin/BooleanPlugin.ts index 761073036e..5f870e60f9 100644 --- a/src/interpreter/plugin/BooleanPlugin.ts +++ b/src/interpreter/plugin/BooleanPlugin.ts @@ -15,98 +15,80 @@ export class BooleanPlugin extends FunctionPlugin { public static implementedFunctions = { 'TRUE': { method: 'literalTrue', - parameters: {list: []}, + parameters: [], }, 'FALSE': { method: 'literalFalse', - parameters: {list: []}, + parameters: [], }, 'IF': { method: 'conditionalIf', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.BOOLEAN}, {argumentType: ArgumentTypes.SCALAR}, {argumentType: ArgumentTypes.SCALAR, defaultValue: false}, - ] - }, + ], }, 'AND': { method: 'and', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.BOOLEAN}, ], repeatLastArg: true, expandRanges: true, - }, }, 'OR': { method: 'or', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.BOOLEAN}, ], repeatLastArg: true, expandRanges: true, - }, }, 'XOR': { method: 'xor', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.BOOLEAN}, ], repeatLastArg: true, expandRanges: true, - }, }, 'NOT': { method: 'not', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.BOOLEAN}, ] - }, }, 'SWITCH': { method: 'switch', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NOERROR}, {argumentType: ArgumentTypes.SCALAR}, {argumentType: ArgumentTypes.SCALAR}, ], repeatLastArg: true, - }, }, 'IFERROR': { method: 'iferror', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.SCALAR}, {argumentType: ArgumentTypes.SCALAR}, ] - }, }, 'IFNA': { method: 'ifna', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.SCALAR}, {argumentType: ArgumentTypes.SCALAR}, ] - }, }, 'CHOOSE': { method: 'choose', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NUMBER}, {argumentType: ArgumentTypes.SCALAR}, ], repeatLastArg: true, - }, }, } @@ -119,7 +101,7 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public literalTrue(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('TRUE'), () => true) + return this.runFunction(ast.args, formulaAddress, this.metadata('TRUE'), () => true) } /** @@ -131,7 +113,7 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public literalFalse(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('FALSE'), () => false) + return this.runFunction(ast.args, formulaAddress, this.metadata('FALSE'), () => false) } /** @@ -143,7 +125,7 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public conditionalIf(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InterpreterValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('IF'), (condition, arg2, arg3) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('IF'), (condition, arg2, arg3) => { return condition ? arg2 : arg3 }) } @@ -157,7 +139,7 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public and(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('AND'), + return this.runFunction(ast.args, formulaAddress, this.metadata('AND'), (...args) => !args.some((arg: boolean) => !arg) ) } @@ -171,17 +153,17 @@ export class BooleanPlugin extends FunctionPlugin { * @param formulaAddress */ public or(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('OR'), + return this.runFunction(ast.args, formulaAddress, this.metadata('OR'), (...args) => args.some((arg: boolean) => arg) ) } public not(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('NOT'), (arg) => !arg) + return this.runFunction(ast.args, formulaAddress, this.metadata('NOT'), (arg) => !arg) } public xor(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('XOR'), (...args) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('XOR'), (...args) => { let cnt = 0 args.forEach((arg: boolean) => { if (arg) { @@ -193,7 +175,7 @@ export class BooleanPlugin extends FunctionPlugin { } public switch(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('SWITCH'), (selector, ...args) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('SWITCH'), (selector, ...args) => { const n = args.length let i = 0 for (; i + 1 < n; i += 2) { @@ -213,7 +195,7 @@ export class BooleanPlugin extends FunctionPlugin { } public iferror(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('IFERROR'), (arg1: InternalScalarValue, arg2: InternalScalarValue) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('IFERROR'), (arg1: InternalScalarValue, arg2: InternalScalarValue) => { if (arg1 instanceof CellError) { return arg2 } else { @@ -223,7 +205,7 @@ export class BooleanPlugin extends FunctionPlugin { } public ifna(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('IFNA'), (arg1: InternalScalarValue, arg2: InternalScalarValue) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('IFNA'), (arg1: InternalScalarValue, arg2: InternalScalarValue) => { if (arg1 instanceof CellError && arg1.type === ErrorType.NA) { return arg2 } else { @@ -233,7 +215,7 @@ export class BooleanPlugin extends FunctionPlugin { } public choose(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('CHOOSE'), (selector, ...args) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('CHOOSE'), (selector, ...args) => { if (selector !== Math.round(selector) || selector < 1 || selector > args.length) { return new CellError(ErrorType.NUM) } diff --git a/src/interpreter/plugin/CharPlugin.ts b/src/interpreter/plugin/CharPlugin.ts index ddf44af14e..684ccaab39 100644 --- a/src/interpreter/plugin/CharPlugin.ts +++ b/src/interpreter/plugin/CharPlugin.ts @@ -11,16 +11,14 @@ export class CharPlugin extends FunctionPlugin { public static implementedFunctions = { 'CHAR': { method: 'char', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NUMBER} ], - } }, } public char(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('CHAR'), (value: number) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('CHAR'), (value: number) => { if (value < 1 || value > 255) { return new CellError(ErrorType.NUM) } diff --git a/src/interpreter/plugin/CodePlugin.ts b/src/interpreter/plugin/CodePlugin.ts index 3c39809f06..92aee59af4 100644 --- a/src/interpreter/plugin/CodePlugin.ts +++ b/src/interpreter/plugin/CodePlugin.ts @@ -11,16 +11,14 @@ export class CodePlugin extends FunctionPlugin { public static implementedFunctions = { 'CODE': { method: 'code', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.STRING} ] - }, }, } public code(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('CODE'), (value: string) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('CODE'), (value: string) => { if (value.length === 0) { return new CellError(ErrorType.VALUE) } diff --git a/src/interpreter/plugin/CountUniquePlugin.ts b/src/interpreter/plugin/CountUniquePlugin.ts index d3c14911ce..c308a6319d 100644 --- a/src/interpreter/plugin/CountUniquePlugin.ts +++ b/src/interpreter/plugin/CountUniquePlugin.ts @@ -14,13 +14,11 @@ export class CountUniquePlugin extends FunctionPlugin { public static implementedFunctions = { 'COUNTUNIQUE': { method: 'countunique', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.SCALAR}, ], repeatLastArg: true, expandRanges: true, - }, }, } @@ -33,7 +31,7 @@ export class CountUniquePlugin extends FunctionPlugin { * @param formulaAddress */ public countunique(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('COUNTUNIQUE'), (...args: InternalScalarValue[]) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('COUNTUNIQUE'), (...args: InternalScalarValue[]) => { const valuesSet = new Set() const errorsSet = new Set() diff --git a/src/interpreter/plugin/DatePlugin.ts b/src/interpreter/plugin/DatePlugin.ts index 6db1939e3d..b10353cb33 100644 --- a/src/interpreter/plugin/DatePlugin.ts +++ b/src/interpreter/plugin/DatePlugin.ts @@ -17,64 +17,50 @@ export class DatePlugin extends FunctionPlugin { public static implementedFunctions = { 'DATE': { method: 'date', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NUMBER}, {argumentType: ArgumentTypes.NUMBER}, {argumentType: ArgumentTypes.NUMBER}, ] - }, }, 'MONTH': { method: 'month', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NUMBER}, ] - }, }, 'YEAR': { method: 'year', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NUMBER}, ] - }, }, 'TEXT': { method: 'text', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NUMBER}, {argumentType: ArgumentTypes.STRING}, ] - }, }, 'EOMONTH': { method: 'eomonth', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NUMBER}, {argumentType: ArgumentTypes.NUMBER}, ] - }, }, 'DAY': { method: 'day', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NUMBER}, ] - }, }, 'DAYS': { method: 'days', - parameters: { - list: [ + parameters:[ {argumentType: ArgumentTypes.NUMBER}, {argumentType: ArgumentTypes.NUMBER}, ] - }, }, } @@ -87,7 +73,7 @@ export class DatePlugin extends FunctionPlugin { * @param formulaAddress */ public date(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('DATE'), (year, month, day) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('DATE'), (year, month, day) => { const d = Math.trunc(day) let m = Math.trunc(month) let y = Math.trunc(year) @@ -110,20 +96,20 @@ export class DatePlugin extends FunctionPlugin { } public eomonth(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('EOMONTH'), (dateNumber, numberOfMonthsToShift) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('EOMONTH'), (dateNumber, numberOfMonthsToShift) => { const date = this.interpreter.dateHelper.numberToSimpleDate(dateNumber) return this.interpreter.dateHelper.dateToNumber(endOfMonth(offsetMonth(date, numberOfMonthsToShift))) }) } public day(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('DAY'), + return this.runFunction(ast.args, formulaAddress, this.metadata('DAY'), (dateNumber) => this.interpreter.dateHelper.numberToSimpleDate(dateNumber).day ) } public days(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('DAYS'), (endDate, startDate) => endDate - startDate) + return this.runFunction(ast.args, formulaAddress, this.metadata('DAYS'), (endDate, startDate) => endDate - startDate) } /** @@ -135,7 +121,7 @@ export class DatePlugin extends FunctionPlugin { * @param formulaAddress */ public month(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('MONTH'), + return this.runFunction(ast.args, formulaAddress, this.metadata('MONTH'), (dateNumber) => this.interpreter.dateHelper.numberToSimpleDate(dateNumber).month ) } @@ -149,7 +135,7 @@ export class DatePlugin extends FunctionPlugin { * @param formulaAddress */ public year(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('YEAR'), + return this.runFunction(ast.args, formulaAddress, this.metadata('YEAR'), (dateNumber) => this.interpreter.dateHelper.numberToSimpleDate(dateNumber).year ) } @@ -163,7 +149,7 @@ export class DatePlugin extends FunctionPlugin { * @param formulaAddress */ public text(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('TEXT'), + return this.runFunction(ast.args, formulaAddress, this.metadata('TEXT'), (numberRepresentation, formatArg) =>format(numberRepresentation, formatArg, this.config, this.interpreter.dateHelper) ) } diff --git a/src/interpreter/plugin/DegreesPlugin.ts b/src/interpreter/plugin/DegreesPlugin.ts index b1e428b366..de0a7e506c 100644 --- a/src/interpreter/plugin/DegreesPlugin.ts +++ b/src/interpreter/plugin/DegreesPlugin.ts @@ -11,16 +11,14 @@ export class DegreesPlugin extends FunctionPlugin { public static implementedFunctions = { 'DEGREES': { method: 'degrees', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NUMBER} ] - }, }, } public degrees(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('DEGREES'), + return this.runFunction(ast.args, formulaAddress, this.metadata('DEGREES'), (arg) => arg * (180 / Math.PI) ) } diff --git a/src/interpreter/plugin/DeltaPlugin.ts b/src/interpreter/plugin/DeltaPlugin.ts index 96bd936da0..97aac059b8 100644 --- a/src/interpreter/plugin/DeltaPlugin.ts +++ b/src/interpreter/plugin/DeltaPlugin.ts @@ -11,17 +11,15 @@ export class DeltaPlugin extends FunctionPlugin { public static implementedFunctions = { 'DELTA': { method: 'delta', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NUMBER}, {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, ] - }, }, } public delta(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('DELTA'), + return this.runFunction(ast.args, formulaAddress, this.metadata('DELTA'), (left: number, right: number) => (left === right ? 1 : 0) ) } diff --git a/src/interpreter/plugin/ErrorFunctionPlugin.ts b/src/interpreter/plugin/ErrorFunctionPlugin.ts index f4cfa362f1..bba1b9528c 100644 --- a/src/interpreter/plugin/ErrorFunctionPlugin.ts +++ b/src/interpreter/plugin/ErrorFunctionPlugin.ts @@ -11,25 +11,21 @@ export class ErrorFunctionPlugin extends FunctionPlugin { public static implementedFunctions = { 'ERF': { method: 'erf', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NUMBER}, {argumentType: ArgumentTypes.NUMBER, optionalArg: true}, ] - }, }, 'ERFC': { method: 'erfc', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NUMBER} ] - }, }, } public erf(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('ERF'), (lowerBound, upperBound) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('ERF'), (lowerBound, upperBound) => { if (upperBound === undefined) { return erf(lowerBound) } else { @@ -39,7 +35,7 @@ export class ErrorFunctionPlugin extends FunctionPlugin { } public erfc(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('ERFC'), erfc) + return this.runFunction(ast.args, formulaAddress, this.metadata('ERFC'), erfc) } } diff --git a/src/interpreter/plugin/ExpPlugin.ts b/src/interpreter/plugin/ExpPlugin.ts index cfe3a97e9a..c96b3eb856 100644 --- a/src/interpreter/plugin/ExpPlugin.ts +++ b/src/interpreter/plugin/ExpPlugin.ts @@ -11,9 +11,9 @@ export class ExpPlugin extends FunctionPlugin { public static implementedFunctions = { 'EXP': { method: 'exp', - parameters: { list: [ + parameters:[ { argumentType: ArgumentTypes.NUMBER } - ]}, + ], }, } @@ -26,6 +26,6 @@ export class ExpPlugin extends FunctionPlugin { * @param formulaAddress */ public exp(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('EXP'), Math.exp) + return this.runFunction(ast.args, formulaAddress, this.metadata('EXP'), Math.exp) } } diff --git a/src/interpreter/plugin/FinancialPlugin.ts b/src/interpreter/plugin/FinancialPlugin.ts index c3e033e554..b72cb343a3 100644 --- a/src/interpreter/plugin/FinancialPlugin.ts +++ b/src/interpreter/plugin/FinancialPlugin.ts @@ -11,20 +11,17 @@ export class FinancialPlugin extends FunctionPlugin { public static implementedFunctions = { 'PMT': { method: 'pmt', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NUMBER}, {argumentType: ArgumentTypes.NUMBER}, {argumentType: ArgumentTypes.NUMBER}, {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, ] - }, }, 'IPMT': { method: 'ipmt', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NUMBER}, {argumentType: ArgumentTypes.NUMBER}, {argumentType: ArgumentTypes.NUMBER}, @@ -32,12 +29,10 @@ export class FinancialPlugin extends FunctionPlugin { {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, ] - }, }, 'PPMT': { method: 'ppmt', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NUMBER}, {argumentType: ArgumentTypes.NUMBER}, {argumentType: ArgumentTypes.NUMBER}, @@ -45,36 +40,33 @@ export class FinancialPlugin extends FunctionPlugin { {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, ] - }, }, 'FV': { method: 'fv', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NUMBER}, {argumentType: ArgumentTypes.NUMBER}, {argumentType: ArgumentTypes.NUMBER}, {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, {argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, ] - }, }, } public pmt(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('PMT'), pmtCore) + return this.runFunction(ast.args, formulaAddress, this.metadata('PMT'), pmtCore) } public ipmt(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('IPMT'), ipmtCore) + return this.runFunction(ast.args, formulaAddress, this.metadata('IPMT'), ipmtCore) } public ppmt(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('PPMT'), ppmtCore) + return this.runFunction(ast.args, formulaAddress, this.metadata('PPMT'), ppmtCore) } public fv(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('FV'), fvCore) + return this.runFunction(ast.args, formulaAddress, this.metadata('FV'), fvCore) } } diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index 9d5dd2f6c0..612aa8c4ba 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -21,10 +21,20 @@ export interface ImplementedFunctions { export interface FunctionMetadata { method: string, - parameters?: FunctionArgumentsDefinition, + parameters?: FunctionArgument[], isVolatile?: boolean, isDependentOnSheetStructureChange?: boolean, doesNotNeedArgumentsToBeComputed?: boolean, + + /** + * Used for functions with variable number of arguments -- last defined argument is repeated indefinitely. + */ + repeatLastArg?: boolean, + + /** + * Ranges in arguments are inlined to (possibly multiple) scalar arguments. + */ + expandRanges?: boolean, } export interface FunctionPluginDefinition { @@ -71,20 +81,6 @@ export enum ArgumentTypes { INTEGER = 'INTEGER', } -export interface FunctionArgumentsDefinition { - list: FunctionArgument[], - - /** - * Used for functions with variable number of arguments -- last defined argument is repeated indefinitely. - */ - repeatLastArg?: boolean, - - /** - * Ranges in arguments are inlined to (possibly multiple) scalar arguments. - */ - expandRanges?: boolean, -} - export interface FunctionArgument { argumentType: ArgumentTypes, @@ -226,10 +222,10 @@ export abstract class FunctionPlugin { protected runFunction = ( args: Ast[], formulaAddress: SimpleCellAddress, - functionDefinition: FunctionArgumentsDefinition, + functionDefinition: FunctionMetadata, fn: (...arg: any) => InternalScalarValue ) => { - const argumentDefinitions: FunctionArgument[] = functionDefinition.list + const argumentDefinitions: FunctionArgument[] = functionDefinition.parameters! let scalarValues: [InterpreterValue, boolean][] if(functionDefinition.expandRanges) { @@ -276,8 +272,8 @@ export abstract class FunctionPlugin { return argCoerceFailure ?? fn(...coercedArguments) } - protected parameters(name: string): FunctionArgumentsDefinition { - const params = (this.constructor as FunctionPluginDefinition).implementedFunctions[name]?.parameters + protected metadata(name: string): FunctionMetadata { + const params = (this.constructor as FunctionPluginDefinition).implementedFunctions[name] if (params !== undefined) { return params } diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index b4deaa3f9b..30ea9e1b10 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -16,39 +16,39 @@ export class InformationPlugin extends FunctionPlugin { public static implementedFunctions = { 'ISERROR': { method: 'iserror', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.SCALAR} - ]} + ] }, 'ISBLANK': { method: 'isblank', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.SCALAR} - ]} + ] }, 'ISNUMBER': { method: 'isnumber', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.SCALAR} - ]} + ] }, 'ISLOGICAL': { method: 'islogical', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.SCALAR} - ]} + ] }, 'ISTEXT': { method: 'istext', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.SCALAR} - ]} + ] }, 'ISNONTEXT': { method: 'isnontext', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.SCALAR} - ]} + ] }, 'COLUMNS': { method: 'columns', @@ -74,7 +74,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public iserror(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('ISERROR'), (arg: InternalScalarValue) => + return this.runFunction(ast.args, formulaAddress, this.metadata('ISERROR'), (arg: InternalScalarValue) => (arg instanceof CellError) ) } @@ -88,7 +88,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public isblank(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('ISBLANK'), (arg: InternalScalarValue) => + return this.runFunction(ast.args, formulaAddress, this.metadata('ISBLANK'), (arg: InternalScalarValue) => (arg === EmptyValue) ) } @@ -102,7 +102,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public isnumber(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('ISNUMBER'), (arg: InternalScalarValue) => + return this.runFunction(ast.args, formulaAddress, this.metadata('ISNUMBER'), (arg: InternalScalarValue) => (typeof arg === 'number') ) } @@ -116,7 +116,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public islogical(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('ISLOGICAL'), (arg: InternalScalarValue) => + return this.runFunction(ast.args, formulaAddress, this.metadata('ISLOGICAL'), (arg: InternalScalarValue) => (typeof arg === 'boolean') ) } @@ -130,7 +130,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public istext(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('ISTEXT'), (arg: InternalScalarValue) => + return this.runFunction(ast.args, formulaAddress, this.metadata('ISTEXT'), (arg: InternalScalarValue) => (typeof arg === 'string') ) } @@ -144,7 +144,7 @@ export class InformationPlugin extends FunctionPlugin { * @param formulaAddress */ public isnontext(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('ISNONTEXT'), (arg: InternalScalarValue) => + return this.runFunction(ast.args, formulaAddress, this.metadata('ISNONTEXT'), (arg: InternalScalarValue) => (typeof arg !== 'string') ) } diff --git a/src/interpreter/plugin/IsEvenPlugin.ts b/src/interpreter/plugin/IsEvenPlugin.ts index 67a3432d49..720d3f6bf5 100644 --- a/src/interpreter/plugin/IsEvenPlugin.ts +++ b/src/interpreter/plugin/IsEvenPlugin.ts @@ -11,14 +11,14 @@ export class IsEvenPlugin extends FunctionPlugin { public static implementedFunctions = { 'ISEVEN': { method: 'iseven', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER} - ]} + ] }, } public iseven(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('ISEVEN'), + return this.runFunction(ast.args, formulaAddress, this.metadata('ISEVEN'), (val) => (val % 2 === 0) ) } diff --git a/src/interpreter/plugin/IsOddPlugin.ts b/src/interpreter/plugin/IsOddPlugin.ts index 3e307575b3..512e24649f 100644 --- a/src/interpreter/plugin/IsOddPlugin.ts +++ b/src/interpreter/plugin/IsOddPlugin.ts @@ -11,16 +11,14 @@ export class IsOddPlugin extends FunctionPlugin { public static implementedFunctions = { 'ISODD': { method: 'isodd', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NUMBER} ] - } }, } public isodd(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('ISODD'), + return this.runFunction(ast.args, formulaAddress, this.metadata('ISODD'), (val) => (val % 2 === 1) ) } diff --git a/src/interpreter/plugin/LogarithmPlugin.ts b/src/interpreter/plugin/LogarithmPlugin.ts index 8346b4a22d..88174492fc 100644 --- a/src/interpreter/plugin/LogarithmPlugin.ts +++ b/src/interpreter/plugin/LogarithmPlugin.ts @@ -12,42 +12,36 @@ export class LogarithmPlugin extends FunctionPlugin { public static implementedFunctions = { 'LOG10': { method: 'log10', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NUMBER} ] - }, }, 'LOG': { method: 'log', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NUMBER, greaterThan: 0}, {argumentType: ArgumentTypes.NUMBER, defaultValue: 10, greaterThan: 0}, ] - }, }, 'LN': { method: 'ln', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NUMBER} ] - }, }, } public log10(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('LOG10'), Math.log10) + return this.runFunction(ast.args, formulaAddress, this.metadata('LOG10'), Math.log10) } public log(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('LOG'), + return this.runFunction(ast.args, formulaAddress, this.metadata('LOG'), (arg: number, base: number) => Math.log(arg) / Math.log(base) ) } public ln(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('LN'), Math.log) + return this.runFunction(ast.args, formulaAddress, this.metadata('LN'), Math.log) } } diff --git a/src/interpreter/plugin/MathConstantsPlugin.ts b/src/interpreter/plugin/MathConstantsPlugin.ts index 174e2f8571..b3cd67e41a 100644 --- a/src/interpreter/plugin/MathConstantsPlugin.ts +++ b/src/interpreter/plugin/MathConstantsPlugin.ts @@ -14,22 +14,22 @@ export class MathConstantsPlugin extends FunctionPlugin { public static implementedFunctions = { 'PI': { method: 'pi', - parameters: {list: []}, + parameters: [], }, 'E': { method: 'e', - parameters: {list: []}, + parameters: [], }, } public pi(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('PI'), + return this.runFunction(ast.args, formulaAddress, this.metadata('PI'), () => PI ) } public e(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('E'), + return this.runFunction(ast.args, formulaAddress, this.metadata('E'), () => E ) } diff --git a/src/interpreter/plugin/MedianPlugin.ts b/src/interpreter/plugin/MedianPlugin.ts index 9d2e80583c..c56315242e 100644 --- a/src/interpreter/plugin/MedianPlugin.ts +++ b/src/interpreter/plugin/MedianPlugin.ts @@ -15,13 +15,11 @@ export class MedianPlugin extends FunctionPlugin { public static implementedFunctions = { 'MEDIAN': { method: 'median', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.NOERROR}, ], repeatLastArg: true, expandRanges: true, - }, }, } @@ -34,7 +32,7 @@ export class MedianPlugin extends FunctionPlugin { * @param formulaAddress */ public median(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('MEDIAN'), (...args) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('MEDIAN'), (...args) => { const values: number[] = args.filter((val: InternalScalarValue) => (typeof val === 'number')) if (values.length === 0) { return new CellError(ErrorType.NUM) diff --git a/src/interpreter/plugin/ModuloPlugin.ts b/src/interpreter/plugin/ModuloPlugin.ts index e91a1ebc69..158a7bf424 100644 --- a/src/interpreter/plugin/ModuloPlugin.ts +++ b/src/interpreter/plugin/ModuloPlugin.ts @@ -11,15 +11,15 @@ export class ModuloPlugin extends FunctionPlugin { public static implementedFunctions = { 'MOD': { method: 'mod', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER }, { argumentType: ArgumentTypes.NUMBER }, - ]}, + ], }, } public mod(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('MOD'), (dividend: number, divisor: number) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('MOD'), (dividend: number, divisor: number) => { if (divisor === 0) { return new CellError(ErrorType.DIV_BY_ZERO) } else { diff --git a/src/interpreter/plugin/PowerPlugin.ts b/src/interpreter/plugin/PowerPlugin.ts index 6d5b4cf5a9..f3fe66a717 100644 --- a/src/interpreter/plugin/PowerPlugin.ts +++ b/src/interpreter/plugin/PowerPlugin.ts @@ -11,14 +11,14 @@ export class PowerPlugin extends FunctionPlugin { public static implementedFunctions = { 'POWER': { method: 'power', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER }, { argumentType: ArgumentTypes.NUMBER }, - ]}, + ], }, } public power(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('POWER'), Math.pow) + return this.runFunction(ast.args, formulaAddress, this.metadata('POWER'), Math.pow) } } diff --git a/src/interpreter/plugin/RadiansPlugin.ts b/src/interpreter/plugin/RadiansPlugin.ts index ed12a09c84..4588dfdd90 100644 --- a/src/interpreter/plugin/RadiansPlugin.ts +++ b/src/interpreter/plugin/RadiansPlugin.ts @@ -11,14 +11,14 @@ export class RadiansPlugin extends FunctionPlugin { public static implementedFunctions = { 'RADIANS': { method: 'radians', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER } - ]}, + ], }, } public radians(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('RADIANS'), + return this.runFunction(ast.args, formulaAddress, this.metadata('RADIANS'), (arg) => arg * (Math.PI / 180) ) } diff --git a/src/interpreter/plugin/RadixConversionPlugin.ts b/src/interpreter/plugin/RadixConversionPlugin.ts index ea6b9891b1..3a3046182d 100644 --- a/src/interpreter/plugin/RadixConversionPlugin.ts +++ b/src/interpreter/plugin/RadixConversionPlugin.ts @@ -19,82 +19,82 @@ export class RadixConversionPlugin extends FunctionPlugin { public static implementedFunctions = { 'DEC2BIN': { method: 'dec2bin', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER, minValue: minValFromBase(2), maxValue: maxValFromBase(2) }, { argumentType: ArgumentTypes.NUMBER, optionalArg: true, minValue: 1, maxValue: 10 }, - ]}, + ], }, 'DEC2OCT': { method: 'dec2oct', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER, minValue: minValFromBase(8), maxValue: maxValFromBase(8) }, { argumentType: ArgumentTypes.NUMBER, optionalArg: true, minValue: 1, maxValue: 10 }, - ]}, + ], }, 'DEC2HEX': { method: 'dec2hex', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER, minValue: minValFromBase(16), maxValue: maxValFromBase(16) }, { argumentType: ArgumentTypes.NUMBER, optionalArg: true, minValue: 1, maxValue: 10 }, - ]}, + ], }, 'BIN2DEC': { method: 'bin2dec', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.STRING } - ]}, + ], }, 'BIN2OCT': { method: 'bin2oct', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.STRING }, { argumentType: ArgumentTypes.NUMBER, optionalArg: true, minValue: 0, maxValue: DECIMAL_NUMBER_OF_BITS}, - ]}, + ], }, 'BIN2HEX': { method: 'bin2hex', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.STRING }, { argumentType: ArgumentTypes.NUMBER, optionalArg: true, minValue: 0, maxValue: DECIMAL_NUMBER_OF_BITS}, - ]}, + ], }, 'DECIMAL': { method: 'decimal', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.STRING }, { argumentType: ArgumentTypes.NUMBER, minValue: MIN_BASE, maxValue: MAX_BASE }, - ]}, + ], }, 'BASE': { method: 'base', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER, minValue: 0 }, { argumentType: ArgumentTypes.NUMBER, minValue: MIN_BASE, maxValue: MAX_BASE }, { argumentType: ArgumentTypes.NUMBER, optionalArg: true, minValue: 0, maxValue: DECIMAL_NUMBER_OF_BITS}, - ]}, + ], }, } public dec2bin(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('DEC2BIN'), + return this.runFunction(ast.args, formulaAddress, this.metadata('DEC2BIN'), (value, places) => decimalToBaseWithExactPadding(value, 2, places) ) } public dec2oct(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('DEC2OCT'), + return this.runFunction(ast.args, formulaAddress, this.metadata('DEC2OCT'), (value, places) => decimalToBaseWithExactPadding(value, 8, places) ) } public dec2hex(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('DEC2HEX'), + return this.runFunction(ast.args, formulaAddress, this.metadata('DEC2HEX'), (value, places) => decimalToBaseWithExactPadding(value, 16, places) ) } public bin2dec(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('BIN2DEC'), (binary) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('BIN2DEC'), (binary) => { const binaryWithSign = coerceStringToBase(binary, 2, NUMBER_OF_BITS) if(binaryWithSign === undefined) { return new CellError(ErrorType.NUM) @@ -104,7 +104,7 @@ export class RadixConversionPlugin extends FunctionPlugin { } public bin2oct(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('BIN2OCT'), (binary, places) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('BIN2OCT'), (binary, places) => { const binaryWithSign = coerceStringToBase(binary, 2, NUMBER_OF_BITS) if(binaryWithSign === undefined) { return new CellError(ErrorType.NUM) @@ -114,7 +114,7 @@ export class RadixConversionPlugin extends FunctionPlugin { } public bin2hex(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('BIN2HEX'), (binary, places) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('BIN2HEX'), (binary, places) => { const binaryWithSign = coerceStringToBase(binary, 2, NUMBER_OF_BITS) if(binaryWithSign === undefined) { return new CellError(ErrorType.NUM) @@ -124,11 +124,11 @@ export class RadixConversionPlugin extends FunctionPlugin { } public base(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('BASE'), decimalToBaseWithMinimumPadding) + return this.runFunction(ast.args, formulaAddress, this.metadata('BASE'), decimalToBaseWithMinimumPadding) } public decimal(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('DECIMAL'), (arg, base) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('DECIMAL'), (arg, base) => { const input = coerceStringToBase(arg, base, DECIMAL_NUMBER_OF_BITS) if(input === undefined) { return new CellError(ErrorType.NUM) diff --git a/src/interpreter/plugin/RandomPlugin.ts b/src/interpreter/plugin/RandomPlugin.ts index 8fa32c1333..4ec0c08650 100644 --- a/src/interpreter/plugin/RandomPlugin.ts +++ b/src/interpreter/plugin/RandomPlugin.ts @@ -11,7 +11,7 @@ export class RandomPlugin extends FunctionPlugin { public static implementedFunctions = { 'RAND': { method: 'rand', - parameters: { list: [] }, + parameters: [], isVolatile: true, }, } @@ -26,6 +26,6 @@ export class RandomPlugin extends FunctionPlugin { * @param formulaAddress */ public rand(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('RAND'), Math.random) + return this.runFunction(ast.args, formulaAddress, this.metadata('RAND'), Math.random) } } diff --git a/src/interpreter/plugin/RoundingPlugin.ts b/src/interpreter/plugin/RoundingPlugin.ts index d60e040538..261b7936a2 100644 --- a/src/interpreter/plugin/RoundingPlugin.ts +++ b/src/interpreter/plugin/RoundingPlugin.ts @@ -21,62 +21,62 @@ export class RoundingPlugin extends FunctionPlugin { public static implementedFunctions = { 'ROUNDUP': { method: 'roundup', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER }, { argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, - ]}, + ], }, 'ROUNDDOWN': { method: 'rounddown', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER }, { argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, - ]}, + ], }, 'ROUND': { method: 'round', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER }, { argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, - ]}, + ], }, 'TRUNC': { method: 'trunc', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER }, { argumentType: ArgumentTypes.NUMBER, defaultValue: 0}, - ]}, + ], }, 'INT': { method: 'intFunc', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER } - ]}, + ], }, 'EVEN': { method: 'even', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER } - ]}, + ], }, 'ODD': { method: 'odd', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER } - ]}, + ], }, 'CEILING': { method: 'ceiling', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER }, { argumentType: ArgumentTypes.NUMBER, defaultValue: 1 }, { argumentType: ArgumentTypes.NUMBER, defaultValue: 0 }, - ]}, + ], }, } public roundup(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('ROUNDDOWN'), (numberToRound: number, places: number): number => { + return this.runFunction(ast.args, formulaAddress, this.metadata('ROUNDDOWN'), (numberToRound: number, places: number): number => { const placesMultiplier = Math.pow(10, places) if (numberToRound < 0) { return -Math.ceil(-numberToRound * placesMultiplier) / placesMultiplier @@ -87,7 +87,7 @@ export class RoundingPlugin extends FunctionPlugin { } public rounddown(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('ROUNDDOWN'), (numberToRound: number, places: number): number => { + return this.runFunction(ast.args, formulaAddress, this.metadata('ROUNDDOWN'), (numberToRound: number, places: number): number => { const placesMultiplier = Math.pow(10, places) if (numberToRound < 0) { return -Math.floor(-numberToRound * placesMultiplier) / placesMultiplier @@ -98,7 +98,7 @@ export class RoundingPlugin extends FunctionPlugin { } public round(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('ROUND'), (numberToRound: number, places: number): number => { + return this.runFunction(ast.args, formulaAddress, this.metadata('ROUND'), (numberToRound: number, places: number): number => { const placesMultiplier = Math.pow(10, places) if (numberToRound < 0) { return -Math.round(-numberToRound * placesMultiplier) / placesMultiplier @@ -113,7 +113,7 @@ export class RoundingPlugin extends FunctionPlugin { } public intFunc(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('INT'), (coercedNumberToRound) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('INT'), (coercedNumberToRound) => { if (coercedNumberToRound < 0) { return -Math.floor(-coercedNumberToRound) } else { @@ -123,7 +123,7 @@ export class RoundingPlugin extends FunctionPlugin { } public even(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('EVEN'), (coercedNumberToRound) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('EVEN'), (coercedNumberToRound) => { if (coercedNumberToRound < 0) { return -findNextEvenNumber(-coercedNumberToRound) } else { @@ -133,7 +133,7 @@ export class RoundingPlugin extends FunctionPlugin { } public odd(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('ODD'), (coercedNumberToRound) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('ODD'), (coercedNumberToRound) => { if (coercedNumberToRound < 0) { return -findNextOddNumber(-coercedNumberToRound) } else { @@ -143,7 +143,7 @@ export class RoundingPlugin extends FunctionPlugin { } public ceiling(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('CEILING'), (value: number, significance: number, mode: number) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('CEILING'), (value: number, significance: number, mode: number) => { if (significance === 0 || value === 0) { return 0 } diff --git a/src/interpreter/plugin/SqrtPlugin.ts b/src/interpreter/plugin/SqrtPlugin.ts index ab3ca3b438..7694aa0d1f 100644 --- a/src/interpreter/plugin/SqrtPlugin.ts +++ b/src/interpreter/plugin/SqrtPlugin.ts @@ -11,13 +11,13 @@ export class SqrtPlugin extends FunctionPlugin { public static implementedFunctions = { 'SQRT': { method: 'sqrt', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER } - ]}, + ], }, } public sqrt(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('SQRT'), Math.sqrt) + return this.runFunction(ast.args, formulaAddress, this.metadata('SQRT'), Math.sqrt) } } diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index 6fa19c67b0..1386fa4283 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -14,101 +14,79 @@ export class TextPlugin extends FunctionPlugin { public static implementedFunctions = { 'CONCATENATE': { method: 'concatenate', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.STRING} ], repeatLastArg: true, expandRanges: true, - }, }, 'SPLIT': { method: 'split', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.STRING}, {argumentType: ArgumentTypes.NUMBER}, ] - }, }, 'LEN': { method: 'len', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.STRING} ] - }, }, 'TRIM': { method: 'trim', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.STRING} ] - }, }, 'PROPER': { method: 'proper', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.STRING} ] - }, }, 'CLEAN': { method: 'clean', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.STRING} ] - }, }, 'REPT': { method: 'rept', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.STRING}, {argumentType: ArgumentTypes.NUMBER}, ] - }, }, 'RIGHT': { method: 'right', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.STRING}, {argumentType: ArgumentTypes.NUMBER, defaultValue: 1}, ] - }, }, 'LEFT': { method: 'left', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.STRING}, {argumentType: ArgumentTypes.NUMBER, defaultValue: 1}, ] - }, }, 'SEARCH': { method: 'search', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.STRING}, {argumentType: ArgumentTypes.STRING}, {argumentType: ArgumentTypes.NUMBER, defaultValue: 1}, ] - }, }, 'FIND': { method: 'find', - parameters: { - list: [ + parameters: [ {argumentType: ArgumentTypes.STRING}, {argumentType: ArgumentTypes.STRING}, {argumentType: ArgumentTypes.NUMBER, defaultValue: 1}, ] - }, } } @@ -121,7 +99,7 @@ export class TextPlugin extends FunctionPlugin { * @param formulaAddress */ public concatenate(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('CONCATENATE'), (...args) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('CONCATENATE'), (...args) => { return ''.concat(...args) }) } @@ -135,7 +113,7 @@ export class TextPlugin extends FunctionPlugin { * @param formulaAddress */ public split(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('SPLIT'), (stringToSplit: string, indexToUse: number) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('SPLIT'), (stringToSplit: string, indexToUse: number) => { const splittedString = stringToSplit.split(' ') if (indexToUse >= splittedString.length || indexToUse < 0) { @@ -147,32 +125,32 @@ export class TextPlugin extends FunctionPlugin { } public len(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('LEN'), (arg: string) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('LEN'), (arg: string) => { return arg.length }) } public trim(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('TRIM'), (arg: string) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('TRIM'), (arg: string) => { return arg.replace(/^ +| +$/g, '').replace(/ +/g, ' ') }) } public proper(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('PROPER'), (arg: string) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('PROPER'), (arg: string) => { return arg.replace(/\w\S*/g, word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase()) }) } public clean(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('CLEAN'), (arg: string) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('CLEAN'), (arg: string) => { // eslint-disable-next-line no-control-regex return arg.replace(/[\u0000-\u001F]/g, '') }) } public rept(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('REPT'), (text: string, count: number) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('REPT'), (text: string, count: number) => { if (count < 0) { return new CellError(ErrorType.VALUE) } @@ -181,7 +159,7 @@ export class TextPlugin extends FunctionPlugin { } public right(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('RIGHT'), (text: string, length: number) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('RIGHT'), (text: string, length: number) => { if (length < 0) { return new CellError(ErrorType.VALUE) } else if (length === 0) { @@ -192,7 +170,7 @@ export class TextPlugin extends FunctionPlugin { } public left(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('LEFT'), (text: string, length: number) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('LEFT'), (text: string, length: number) => { if (length < 0) { return new CellError(ErrorType.VALUE) } @@ -201,7 +179,7 @@ export class TextPlugin extends FunctionPlugin { } public search(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('SEARCH'), (pattern, text: string, startIndex: number) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('SEARCH'), (pattern, text: string, startIndex: number) => { if (startIndex < 1 || startIndex > text.length) { return new CellError(ErrorType.VALUE) } @@ -221,7 +199,7 @@ export class TextPlugin extends FunctionPlugin { } public find(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('FIND'), (pattern, text: string, startIndex: number) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('FIND'), (pattern, text: string, startIndex: number) => { if (startIndex < 1 || startIndex > text.length) { return new CellError(ErrorType.VALUE) } diff --git a/src/interpreter/plugin/TrigonometryPlugin.ts b/src/interpreter/plugin/TrigonometryPlugin.ts index dd17e22f2f..d852c661d9 100644 --- a/src/interpreter/plugin/TrigonometryPlugin.ts +++ b/src/interpreter/plugin/TrigonometryPlugin.ts @@ -15,52 +15,52 @@ export class TrigonometryPlugin extends FunctionPlugin { public static implementedFunctions = { 'ACOS': { method: 'acos', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER } - ]} + ] }, 'ASIN': { method: 'asin', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER } - ]} + ] }, 'COS': { method: 'cos', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER } - ]} + ] }, 'SIN': { method: 'sin', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER } - ]} + ] }, 'TAN': { method: 'tan', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER } - ]} + ] }, 'ATAN': { method: 'atan', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER } - ]} + ] }, 'ATAN2': { method: 'atan2', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER }, { argumentType: ArgumentTypes.NUMBER }, - ]} + ] }, 'COT': { method: 'ctg', - parameters: { list: [ + parameters: [ { argumentType: ArgumentTypes.NUMBER } - ]} + ] }, } @@ -73,35 +73,35 @@ export class TrigonometryPlugin extends FunctionPlugin { * @param formulaAddress */ public acos(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('ACOS'), Math.acos) + return this.runFunction(ast.args, formulaAddress, this.metadata('ACOS'), Math.acos) } public asin(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('ASIN'), Math.asin) + return this.runFunction(ast.args, formulaAddress, this.metadata('ASIN'), Math.asin) } public cos(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('COS'), Math.cos) + return this.runFunction(ast.args, formulaAddress, this.metadata('COS'), Math.cos) } public sin(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('SIN'), Math.sin) + return this.runFunction(ast.args, formulaAddress, this.metadata('SIN'), Math.sin) } public tan(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('TAN'), Math.tan) + return this.runFunction(ast.args, formulaAddress, this.metadata('TAN'), Math.tan) } public atan(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('ATAN'), Math.atan) + return this.runFunction(ast.args, formulaAddress, this.metadata('ATAN'), Math.atan) } public atan2(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('ATAN2'), Math.atan2) + return this.runFunction(ast.args, formulaAddress, this.metadata('ATAN2'), Math.atan2) } public ctg(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunction(ast.args, formulaAddress, this.parameters('COT'), (coercedArg) => { + return this.runFunction(ast.args, formulaAddress, this.metadata('COT'), (coercedArg) => { if (coercedArg === 0) { return new CellError(ErrorType.DIV_BY_ZERO) } else { diff --git a/test/interpreter/function-vlookup.spec.ts b/test/interpreter/function-vlookup.spec.ts index 368c304b01..1debbfe2ca 100644 --- a/test/interpreter/function-vlookup.spec.ts +++ b/test/interpreter/function-vlookup.spec.ts @@ -6,7 +6,7 @@ import {Sheet} from '../../src/Sheet' const sharedExamples = (builder: (sheet: Sheet, config?: Partial) => HyperFormula) => { describe('VLOOKUP - args validation', () => { - it('not enough parameters', () => { + it('not enough metadata', () => { const engine = builder([ ['=VLOOKUP(1, A2:B3)'], ]) @@ -14,7 +14,7 @@ const sharedExamples = (builder: (sheet: Sheet, config?: Partial) expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NA)) }) - it('too many parameters', () => { + it('too many metadata', () => { const engine = builder([ ['=VLOOKUP(1, A2:B3, 2, TRUE(), "foo")'], ]) diff --git a/test/optional-parameters.spec.ts b/test/optional-parameters.spec.ts index ff4c05231d..4f61452cac 100644 --- a/test/optional-parameters.spec.ts +++ b/test/optional-parameters.spec.ts @@ -8,23 +8,21 @@ class FooPlugin extends FunctionPlugin { public static implementedFunctions = { 'FOO': { method: 'foo', - parameters: { - list: [ + parameters: [ { argumentType: ArgumentTypes.STRING, defaultValue: 'default1'}, { argumentType: ArgumentTypes.STRING, defaultValue: 'default2'}, ], - }, }, } public foo(ast: ProcedureAst, formulaAddress: SimpleCellAddress) { - return this.runFunction(ast.args, formulaAddress, this.parameters('FOO'), + return this.runFunction(ast.args, formulaAddress, this.metadata('FOO'), (arg1, arg2) => arg1+'+'+arg2 ) } } -describe('Nonexistent parameters', () => { +describe('Nonexistent metadata', () => { it('should work for function', () => { HyperFormula.getLanguage('enGB').extendFunctions({FOO: 'FOO'}) const engine = HyperFormula.buildFromArray([ From 9afe56fda6324dca01a6590e13c31b9128e5535b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Uzna=C5=84ski?= <40573492+izulin@users.noreply.github.com> Date: Tue, 4 Aug 2020 13:40:20 +0200 Subject: [PATCH 89/97] Update test/interpreter/function-vlookup.spec.ts --- test/interpreter/function-vlookup.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/interpreter/function-vlookup.spec.ts b/test/interpreter/function-vlookup.spec.ts index 1debbfe2ca..195761e4d6 100644 --- a/test/interpreter/function-vlookup.spec.ts +++ b/test/interpreter/function-vlookup.spec.ts @@ -14,7 +14,7 @@ const sharedExamples = (builder: (sheet: Sheet, config?: Partial) expect(engine.getCellValue(adr('A1'))).toEqual(detailedError(ErrorType.NA)) }) - it('too many metadata', () => { + it('too many parameters', () => { const engine = builder([ ['=VLOOKUP(1, A2:B3, 2, TRUE(), "foo")'], ]) From e781c432d19b88e842c0e85684c51619369c854d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Uzna=C5=84ski?= <40573492+izulin@users.noreply.github.com> Date: Tue, 4 Aug 2020 13:40:27 +0200 Subject: [PATCH 90/97] Update test/interpreter/function-vlookup.spec.ts --- test/interpreter/function-vlookup.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/interpreter/function-vlookup.spec.ts b/test/interpreter/function-vlookup.spec.ts index 195761e4d6..368c304b01 100644 --- a/test/interpreter/function-vlookup.spec.ts +++ b/test/interpreter/function-vlookup.spec.ts @@ -6,7 +6,7 @@ import {Sheet} from '../../src/Sheet' const sharedExamples = (builder: (sheet: Sheet, config?: Partial) => HyperFormula) => { describe('VLOOKUP - args validation', () => { - it('not enough metadata', () => { + it('not enough parameters', () => { const engine = builder([ ['=VLOOKUP(1, A2:B3)'], ]) From 1fe8a7f08f0edd0d49fac115278710576f4f3eb9 Mon Sep 17 00:00:00 2001 From: izulin Date: Tue, 4 Aug 2020 14:45:31 +0200 Subject: [PATCH 91/97] merge --- src/interpreter/plugin/FormulaTextPlugin.ts | 13 ++++++------- src/interpreter/plugin/FunctionPlugin.ts | 18 ++++++++++-------- src/interpreter/plugin/InformationPlugin.ts | 16 ++++++---------- 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/src/interpreter/plugin/FormulaTextPlugin.ts b/src/interpreter/plugin/FormulaTextPlugin.ts index 9c53141bb6..b5dafd805e 100644 --- a/src/interpreter/plugin/FormulaTextPlugin.ts +++ b/src/interpreter/plugin/FormulaTextPlugin.ts @@ -6,16 +6,15 @@ import {ProcedureAst} from '../../parser' import {CellError, ErrorType, InternalScalarValue, SimpleCellAddress} from '../../Cell' import {FunctionPlugin} from '../index' +import {ArgumentTypes} from './FunctionPlugin' export class FormulaTextPlugin extends FunctionPlugin { public static implementedFunctions = { 'FORMULATEXT': { method: 'formulatext', - parameters: { - list: [ - {argumentType: 'noerror'} - ] - }, + parameters: [ + {argumentType: ArgumentTypes.NOERROR} + ], doesNotNeedArgumentsToBeComputed: true, isDependentOnSheetStructureChange: true }, @@ -30,10 +29,10 @@ export class FormulaTextPlugin extends FunctionPlugin { * @param formulaAddress * */ public formulatext(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { - return this.runFunctionWithReferenceArgument(ast.args, formulaAddress, this.parameters('FORMULATEXT'), + return this.runFunctionWithReferenceArgument(ast.args, formulaAddress, this.metadata('FORMULATEXT'), () => new CellError(ErrorType.NA), (cellReference: SimpleCellAddress) => this.serialization.getCellFormula(cellReference) || new CellError(ErrorType.NA), () => new CellError(ErrorType.NA) ) } -} \ No newline at end of file +} diff --git a/src/interpreter/plugin/FunctionPlugin.ts b/src/interpreter/plugin/FunctionPlugin.ts index cc4ad5056f..a7d471fbfd 100644 --- a/src/interpreter/plugin/FunctionPlugin.ts +++ b/src/interpreter/plugin/FunctionPlugin.ts @@ -19,13 +19,8 @@ export interface ImplementedFunctions { [formulaId: string]: FunctionMetadata, } -export interface FunctionMetadata { - method: string, +export interface FunctionArguments { parameters?: FunctionArgument[], - isVolatile?: boolean, - isDependentOnSheetStructureChange?: boolean, - doesNotNeedArgumentsToBeComputed?: boolean, - /** * Used for functions with variable number of arguments -- last defined argument is repeated indefinitely. */ @@ -37,6 +32,13 @@ export interface FunctionMetadata { expandRanges?: boolean, } +export interface FunctionMetadata extends FunctionArguments{ + method: string, + isVolatile?: boolean, + isDependentOnSheetStructureChange?: boolean, + doesNotNeedArgumentsToBeComputed?: boolean, +} + export interface FunctionPluginDefinition { new(interpreter: Interpreter): FunctionPlugin, @@ -222,7 +224,7 @@ export abstract class FunctionPlugin { protected runFunction = ( args: Ast[], formulaAddress: SimpleCellAddress, - functionDefinition: FunctionMetadata, + functionDefinition: FunctionArguments, fn: (...arg: any) => InternalScalarValue ) => { const argumentDefinitions: FunctionArgument[] = functionDefinition.parameters! @@ -275,7 +277,7 @@ export abstract class FunctionPlugin { protected runFunctionWithReferenceArgument = ( args: Ast[], formulaAddress: SimpleCellAddress, - argumentDefinitions: FunctionMetadata, + argumentDefinitions: FunctionArguments, noArgCallback: () => InternalScalarValue, referenceCallback: (reference: SimpleCellAddress) => InternalScalarValue, nonReferenceCallback: (...arg: any) => InternalScalarValue diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index 5cdf95f449..af84d5eee4 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -100,20 +100,16 @@ export class InformationPlugin extends FunctionPlugin { }, 'SHEET': { method: 'sheet', - parameters: { - list: [ - {argumentType: 'noerror'} - ] - }, + parameters: [ + {argumentType: ArgumentTypes.NOERROR} + ], doesNotNeedArgumentsToBeComputed: true }, 'SHEETS': { method: 'sheets', - parameters: { - list: [ - {argumentType: 'noerror'} - ] - }, + parameters: [ + {argumentType: ArgumentTypes.NOERROR} + ], doesNotNeedArgumentsToBeComputed: true } } From 5c2eb0beeca06b6415eb765847a015f5dc3e9404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Uzna=C5=84ski?= <40573492+izulin@users.noreply.github.com> Date: Tue, 4 Aug 2020 15:52:22 +0200 Subject: [PATCH 92/97] Update CHANGELOG.md Co-authored-by: Wojciech Czerniak --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf09d3e0c6..b8ee239d30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added helper methods for keeping track of cell/range dependencies: getCellPrecedents and getCellDependents. (#441) - Added 4 financial functions FV, PMT, PPMT, IPMT. - Added FORMULATEXT function. -- Added 8 information functions ISERR, ISNA, ISREF, NA, SHEET, SHEETS, ISBINARY, ISFORMULA +- Added 8 information functions ISERR, ISNA, ISREF, NA, SHEET, SHEETS, ISBINARY, ISFORMULA (#481) ### Changed - Operation `moveCells` creating cyclic dependencies does not cause losing original formula. (#479) From e5b8da37dd36590fa109b5f81137b52116658218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Uzna=C5=84ski?= <40573492+izulin@users.noreply.github.com> Date: Tue, 4 Aug 2020 15:52:40 +0200 Subject: [PATCH 93/97] Update src/interpreter/plugin/InformationPlugin.ts Co-authored-by: Wojciech Czerniak --- src/interpreter/plugin/InformationPlugin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index 092d5e38aa..65e8a26de6 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -108,8 +108,8 @@ export class InformationPlugin extends FunctionPlugin { 'SHEETS': { method: 'sheets', parameters: [ - {argumentType: ArgumentTypes.NOERROR} - ], + {argumentType: ArgumentTypes.NOERROR} + ], doesNotNeedArgumentsToBeComputed: true } } From e0813564a86516f20c6fa5192e678b0715467741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Uzna=C5=84ski?= <40573492+izulin@users.noreply.github.com> Date: Tue, 4 Aug 2020 15:52:50 +0200 Subject: [PATCH 94/97] Update src/interpreter/plugin/InformationPlugin.ts Co-authored-by: Wojciech Czerniak --- src/interpreter/plugin/InformationPlugin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index 65e8a26de6..42222c8f45 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -101,8 +101,8 @@ export class InformationPlugin extends FunctionPlugin { 'SHEET': { method: 'sheet', parameters: [ - {argumentType: ArgumentTypes.NOERROR} - ], + {argumentType: ArgumentTypes.NOERROR} + ], doesNotNeedArgumentsToBeComputed: true }, 'SHEETS': { From 6f7032d59541b9dc71b971326eca3bda9f8f6ba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Uzna=C5=84ski?= <40573492+izulin@users.noreply.github.com> Date: Tue, 4 Aug 2020 15:53:02 +0200 Subject: [PATCH 95/97] Update src/interpreter/plugin/InformationPlugin.ts Co-authored-by: Wojciech Czerniak --- src/interpreter/plugin/InformationPlugin.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/interpreter/plugin/InformationPlugin.ts b/src/interpreter/plugin/InformationPlugin.ts index 42222c8f45..cba11aeeb4 100644 --- a/src/interpreter/plugin/InformationPlugin.ts +++ b/src/interpreter/plugin/InformationPlugin.ts @@ -23,33 +23,33 @@ export class InformationPlugin extends FunctionPlugin { 'ISBINARY': { method: 'isbinary', parameters: [ - {argumentType: ArgumentTypes.STRING} - ] + {argumentType: ArgumentTypes.STRING} + ] }, 'ISERR': { method: 'iserr', parameters: [ - {argumentType: ArgumentTypes.SCALAR} - ] + {argumentType: ArgumentTypes.SCALAR} + ] }, 'ISFORMULA': { method: 'isformula', parameters: [ - {argumentType: ArgumentTypes.NOERROR} - ], + {argumentType: ArgumentTypes.NOERROR} + ], doesNotNeedArgumentsToBeComputed: true }, 'ISNA': { method: 'isna', parameters: [ - {argumentType: ArgumentTypes.SCALAR} - ] + {argumentType: ArgumentTypes.SCALAR} + ] }, 'ISREF': { method: 'isref', parameters:[ - {argumentType: ArgumentTypes.SCALAR} - ] + {argumentType: ArgumentTypes.SCALAR} + ] }, 'ISERROR': { method: 'iserror', From bbd74dc382e62c01a3d83b895816be33c5277fbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Uzna=C5=84ski?= <40573492+izulin@users.noreply.github.com> Date: Tue, 4 Aug 2020 15:53:17 +0200 Subject: [PATCH 96/97] Update src/interpreter/plugin/FormulaTextPlugin.ts Co-authored-by: Wojciech Czerniak --- src/interpreter/plugin/FormulaTextPlugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interpreter/plugin/FormulaTextPlugin.ts b/src/interpreter/plugin/FormulaTextPlugin.ts index b5dafd805e..bbcdb685ed 100644 --- a/src/interpreter/plugin/FormulaTextPlugin.ts +++ b/src/interpreter/plugin/FormulaTextPlugin.ts @@ -27,7 +27,7 @@ export class FormulaTextPlugin extends FunctionPlugin { * * @param ast * @param formulaAddress - * */ + */ public formulatext(ast: ProcedureAst, formulaAddress: SimpleCellAddress): InternalScalarValue { return this.runFunctionWithReferenceArgument(ast.args, formulaAddress, this.metadata('FORMULATEXT'), () => new CellError(ErrorType.NA), From c550f5b7d74de8bb24ed1c7dde200c5261ca0bf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Uzna=C5=84ski?= <40573492+izulin@users.noreply.github.com> Date: Tue, 4 Aug 2020 15:53:45 +0200 Subject: [PATCH 97/97] Update src/interpreter/plugin/FormulaTextPlugin.ts Co-authored-by: Wojciech Czerniak --- src/interpreter/plugin/FormulaTextPlugin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interpreter/plugin/FormulaTextPlugin.ts b/src/interpreter/plugin/FormulaTextPlugin.ts index bbcdb685ed..23cae60840 100644 --- a/src/interpreter/plugin/FormulaTextPlugin.ts +++ b/src/interpreter/plugin/FormulaTextPlugin.ts @@ -13,8 +13,8 @@ export class FormulaTextPlugin extends FunctionPlugin { 'FORMULATEXT': { method: 'formulatext', parameters: [ - {argumentType: ArgumentTypes.NOERROR} - ], + {argumentType: ArgumentTypes.NOERROR} + ], doesNotNeedArgumentsToBeComputed: true, isDependentOnSheetStructureChange: true },