diff --git a/jest.config.js b/jest.config.cjs similarity index 100% rename from jest.config.js rename to jest.config.cjs diff --git a/src/grammar/bettermath.ts b/src/grammar/bettermath.ts index 364898d..4f6568c 100644 --- a/src/grammar/bettermath.ts +++ b/src/grammar/bettermath.ts @@ -10,6 +10,8 @@ import { NumberType, IValueType, BettermathGrammarParser, + IRefType, + RefType, } from "./definitions"; import { FunctionType, IFunctionArg } from "./functions/types"; import { FunctionRegistry } from "./functions"; @@ -262,7 +264,13 @@ const buildGrammar = (functionRegistry: FunctionRegistry): BettermathGrammarPars (str: string, indexInfo: Index) => new NumberType(indexInfo, str) ).desc("number"); - const ExpressionValue = P.alt | IValueType>(Num, QuotedString); + const Ref: P.Parser = P.seqMap( + P.regexp(/[A-Z]+[1-9]+[0-9]*/).notFollowedBy(Word), + P.index, + (str: string, indexInfo: Index) => new RefType(indexInfo, str) + ).desc("ref"); + + const ExpressionValue = P.alt | IValueType>(Num, QuotedString, Ref); const ImmediateValue = P.alt | IValueType>( Num.notFollowedBy(RawString), RawString, diff --git a/src/grammar/definitions.ts b/src/grammar/definitions.ts index edc99f4..7bec2dc 100644 --- a/src/grammar/definitions.ts +++ b/src/grammar/definitions.ts @@ -14,10 +14,41 @@ export enum Types { type AllowedType = Types; +export class ValueResolvingResult { + readonly isError: boolean; + readonly value?: T; + readonly error?: Error; + + private constructor(isError: boolean, result?: {value? : T, error?: Error}) { + this.isError = isError; + this.value = result?.value; + this.error = result?.error; + } + + static success = (value: T) => new ValueResolvingResult(false, {value}); + + static error = (error: Error) => new ValueResolvingResult(true, {error}); + + get = () => { + if (this.isError) { + throw new Error( + "The ValueResolvingResult is an error. Cannot get value. Inner error is: " + + this.error + ); + } + + return this.value as T; + } + + getOrElse = (elseValue: T) => this.isError ? elseValue : this.value; + + getError = () => this.error; +} + export interface IBaseType { type: AllowedType; indexInfo: Index; - getValue: () => T; + getValue: (dependencyValueMap: Map | undefined>) => ValueResolvingResult; validate: () => ValidationResult; @@ -28,7 +59,7 @@ export abstract class BaseType implements IBaseType { abstract type: AllowedType; readonly indexInfo: Index; - abstract getValue: () => T; + abstract getValue: (dependencyValueMap: Map | undefined>) => ValueResolvingResult; abstract validate: () => ValidationResult; constructor(indexInfo: Index) { @@ -69,7 +100,7 @@ export class StringType extends BaseType implements IStringType { this.value = str; } - getValue = () => this.value; + getValue = () => ValueResolvingResult.success(this.value); validate = () => makeSuccess(); } @@ -87,7 +118,35 @@ export class NumberType extends BaseType implements INumberType { this.value = Number(num); } - getValue = () => this.value; + getValue = () => ValueResolvingResult.success(this.value); + validate = () => makeSuccess(); +} + +export interface IRefType extends IBaseType { + type: Types.REF; + value: any| undefined; +} + +export class RefType extends BaseType implements IRefType { + readonly type = Types.REF; + readonly value: string; + + constructor(indexInfo: Index, refId: string) { + super(indexInfo); + this.value = refId; + } + + getValue = (dependencyValueMap: Map | undefined>) => { + const referencedElem = dependencyValueMap.get(this.value); + if (!referencedElem) { + // Returning undefined value as success, since some functions may take advanatage of this, even though the value does not exist. + // Specific ValueResolvingResult.error will be returned if some function is processing an undefined Ref when it can't + return ValueResolvingResult.success(undefined) + } else { + return referencedElem.getValue(dependencyValueMap) + } + } + validate = () => makeSuccess(); } diff --git a/src/grammar/functions/base/Add.ts b/src/grammar/functions/base/Add.ts index 08911f2..f31ab56 100644 --- a/src/grammar/functions/base/Add.ts +++ b/src/grammar/functions/base/Add.ts @@ -1,9 +1,10 @@ import type { Index } from "parsimmon"; -import { Types } from "../../definitions"; +import { IBaseType, Types, ValueResolvingResult } from "../../definitions"; import { ValidationError } from "../validator"; import { FunctionArgsValidator, FunctionType, IFunctionArg } from "../types"; import { CommonValidators, PipelineValidator } from "../validator/pipeline"; import { is } from "../validator/argTypeValidator"; +import { resolveValuesOnlyNumbers } from "../util"; export class AddFunction extends FunctionType { readonly returnType = Types.NUMBER; @@ -12,7 +13,14 @@ export class AddFunction extends FunctionType { super(indexInfo, "Add", args); } - getValue = () => this.args[0].getValue() + this.args[1].getValue(); + getValue = (dependencyValueMap: Map | undefined>) => { + try { + const resolvedValues = resolveValuesOnlyNumbers(this.args.map(arg => arg.getValue(dependencyValueMap))) + return ValueResolvingResult.success(resolvedValues[0] + resolvedValues[1]); + } catch (e) { + return ValueResolvingResult.error(e as Error) + } + } protected validateArgs: FunctionArgsValidator = (validator: PipelineValidator, args: IFunctionArg[], onSuccess: () => void, onFailure: (_: ValidationError[]) => void) => { diff --git a/src/grammar/functions/base/Concat.ts b/src/grammar/functions/base/Concat.ts index bf32f06..05a8d8e 100644 --- a/src/grammar/functions/base/Concat.ts +++ b/src/grammar/functions/base/Concat.ts @@ -1,12 +1,20 @@ import type { Index } from "parsimmon"; -import { Types } from "../../definitions"; +import { IBaseType, Types, ValueResolvingResult } from "../../definitions"; import { FunctionType, IFunctionArg } from "../types"; export class ConcatFunction extends FunctionType { readonly returnType = Types.STRING; - constructor(indexInfo: Index, args: IFunctionArg[]) { + constructor(indexInfo: Index, args: (IFunctionArg | IFunctionArg)[]) { super(indexInfo, "CONCAT", args); } - getValue = () => this.args.reduce((acc, arg) => acc + arg.getValue(), ""); + getValue = (dependencyValueMap: Map | undefined>) => { + try { + return ValueResolvingResult.success( + this.args.reduce((acc, arg) => acc + arg.getValue(dependencyValueMap).getOrElse(""), "") + ) + } catch (e) { + return ValueResolvingResult.error(e as Error) + } + } } diff --git a/src/grammar/functions/base/Divide.ts b/src/grammar/functions/base/Divide.ts index 420018c..490342e 100644 --- a/src/grammar/functions/base/Divide.ts +++ b/src/grammar/functions/base/Divide.ts @@ -1,9 +1,10 @@ import type { Index } from "parsimmon"; -import { Types } from "../../definitions"; +import { IBaseType, Types, ValueResolvingResult } from "../../definitions"; import { FunctionArgsValidator, FunctionType, IFunctionArg } from "../types"; import { ValidationError } from "../validator"; import { CommonValidators, PipelineValidator } from "../validator/pipeline"; import { is } from "../validator/argTypeValidator"; +import { resolveValuesOnlyNumbers } from "../util"; export class DivideFunction extends FunctionType { readonly returnType = Types.NUMBER; @@ -12,7 +13,14 @@ export class DivideFunction extends FunctionType { super(indexInfo, "Divide", args); } - getValue = () => this.args[0].getValue() / this.args[1].getValue(); + getValue = (dependencyValueMap: Map | undefined>) => { + try { + const resolvedValues = resolveValuesOnlyNumbers(this.args.map(arg => arg.getValue(dependencyValueMap))) + return ValueResolvingResult.success(resolvedValues[0] / resolvedValues[1]); + } catch (e) { + return ValueResolvingResult.error(e as Error) + } + } protected validateArgs: FunctionArgsValidator = (validator: PipelineValidator, args: IFunctionArg[], onSuccess: () => void, onFailure: (_: ValidationError[]) => void) => { diff --git a/src/grammar/functions/base/Exponentiate.ts b/src/grammar/functions/base/Exponentiate.ts index 6872c6f..06a03d9 100644 --- a/src/grammar/functions/base/Exponentiate.ts +++ b/src/grammar/functions/base/Exponentiate.ts @@ -1,9 +1,10 @@ import type { Index } from "parsimmon"; -import { Types } from "../../definitions"; +import { IBaseType, Types, ValueResolvingResult } from "../../definitions"; import { FunctionArgsValidator, FunctionType, IFunctionArg } from "../types"; import { ValidationError } from "../validator"; import { CommonValidators, PipelineValidator } from "../validator/pipeline"; import { is } from "../validator/argTypeValidator"; +import { resolveValuesOnlyNumbers } from "../util"; export class ExponentiateFunction extends FunctionType { readonly returnType = Types.NUMBER; @@ -12,7 +13,14 @@ export class ExponentiateFunction extends FunctionType { super(indexInfo, "Exponentiate", args); } - getValue = () => Math.pow(this.args[0].getValue(), this.args[1].getValue()); + getValue = (dependencyValueMap: Map | undefined>) => { + try { + const resolvedValues = resolveValuesOnlyNumbers(this.args.map(arg => arg.getValue(dependencyValueMap))) + return ValueResolvingResult.success(Math.pow(resolvedValues[0], resolvedValues[1])); + } catch (e) { + return ValueResolvingResult.error(e as Error) + } + } protected validateArgs: FunctionArgsValidator = (validator: PipelineValidator, args: IFunctionArg[], onSuccess: () => void, onFailure: (_: ValidationError[]) => void) => { diff --git a/src/grammar/functions/base/Factorial.ts b/src/grammar/functions/base/Factorial.ts index 9c01b73..163753a 100644 --- a/src/grammar/functions/base/Factorial.ts +++ b/src/grammar/functions/base/Factorial.ts @@ -1,9 +1,10 @@ import { Index } from "parsimmon"; -import { Types } from "../../definitions"; +import { IBaseType, Types, ValueResolvingResult } from "../../definitions"; import { FunctionArgsValidator, FunctionType, IFunctionArg } from "../types"; import { ValidationError } from "../validator"; import { CommonValidators, PipelineValidator } from "../validator/pipeline"; import { is } from "../validator/argTypeValidator"; +import { resolveValuesOnlyNumbers } from "../util"; export class FactorialFunction extends FunctionType { readonly returnType = Types.NUMBER; @@ -12,7 +13,14 @@ export class FactorialFunction extends FunctionType { super(indexInfo, "Factorial", args); } - getValue = () => this.calculateFactorial(this.args[0].getValue()); + getValue = (dependencyValueMap: Map | undefined>) => { + try { + const resolvedValues = resolveValuesOnlyNumbers([this.args[0].getValue(dependencyValueMap)]) + return ValueResolvingResult.success(this.calculateFactorial(resolvedValues[0])); + } catch (e) { + return ValueResolvingResult.error(e as Error) + } + } validateArgs: FunctionArgsValidator = (validator: PipelineValidator, args: IFunctionArg[], onSuccess: () => void, onFailure: (errors: ValidationError[]) => void) => { diff --git a/src/grammar/functions/base/Multiply.ts b/src/grammar/functions/base/Multiply.ts index 6f702f6..11a638a 100644 --- a/src/grammar/functions/base/Multiply.ts +++ b/src/grammar/functions/base/Multiply.ts @@ -1,9 +1,10 @@ import type { Index } from "parsimmon"; -import { Types } from "../../definitions"; +import { IBaseType, Types, ValueResolvingResult } from "../../definitions"; import { FunctionArgsValidator, FunctionType, IFunctionArg } from "../types"; import { ValidationError } from "../validator"; import { CommonValidators, PipelineValidator } from "../validator/pipeline"; import { is } from "../validator/argTypeValidator"; +import { resolveValuesOnlyNumbers } from "../util"; export class MultiplyFunction extends FunctionType { readonly returnType = Types.NUMBER; @@ -11,7 +12,14 @@ export class MultiplyFunction extends FunctionType { super(indexInfo, "Multiply", args); } - getValue = () => this.args[0].getValue() * this.args[1].getValue(); + getValue = (dependencyValueMap: Map | undefined>) => { + try { + const resolvedValues = resolveValuesOnlyNumbers(this.args.map(arg => arg.getValue(dependencyValueMap))) + return ValueResolvingResult.success(resolvedValues[0] * resolvedValues[1]); + } catch (e) { + return ValueResolvingResult.error(e as Error) + } + } protected validateArgs: FunctionArgsValidator = (validator: PipelineValidator, args: IFunctionArg[], onSuccess: () => void, onFailure: (_: ValidationError[]) => void) => { diff --git a/src/grammar/functions/base/Negate.ts b/src/grammar/functions/base/Negate.ts index 0634392..05245ff 100644 --- a/src/grammar/functions/base/Negate.ts +++ b/src/grammar/functions/base/Negate.ts @@ -1,9 +1,10 @@ import type { Index } from "parsimmon"; -import { Types } from "../../definitions"; +import { IBaseType, Types, ValueResolvingResult } from "../../definitions"; import { ValidationError } from "../validator"; import { FunctionArgsValidator, FunctionType, IFunctionArg } from "../types"; import { CommonValidators, PipelineValidator } from "../validator/pipeline"; import { is } from "../validator/argTypeValidator"; +import { resolveValuesOnlyNumbers } from "../util"; export class NegateFunction extends FunctionType { readonly returnType = Types.NUMBER; @@ -12,7 +13,16 @@ export class NegateFunction extends FunctionType { super(indexInfo, "Negate", args); } - getValue = () => -this.args[0].getValue(); + + getValue = (dependencyValueMap: Map | undefined>) => { + try { + const resolvedValues = resolveValuesOnlyNumbers([this.args[0].getValue(dependencyValueMap)]) + return ValueResolvingResult.success(-resolvedValues[0]); + } catch (e) { + return ValueResolvingResult.error(e as Error) + } + } + validateArgs: FunctionArgsValidator = (validator: PipelineValidator, args: IFunctionArg[], onSuccess: () => void, onFailure: (errors: ValidationError[]) => void) => { diff --git a/src/grammar/functions/base/Subtract.ts b/src/grammar/functions/base/Subtract.ts index 69b9d1b..dac1e0a 100644 --- a/src/grammar/functions/base/Subtract.ts +++ b/src/grammar/functions/base/Subtract.ts @@ -1,9 +1,10 @@ import type { Index } from "parsimmon"; -import { Types } from "../../definitions"; +import { IBaseType, Types, ValueResolvingResult } from "../../definitions"; import { FunctionArgsValidator, FunctionType, IFunctionArg } from "../types"; import { ValidationError } from "../validator"; import { CommonValidators, PipelineValidator } from "../validator/pipeline"; import { is } from "../validator/argTypeValidator"; +import { resolveValuesOnlyNumbers } from "../util"; export class SubtractFunction extends FunctionType { readonly returnType = Types.NUMBER; @@ -12,7 +13,14 @@ export class SubtractFunction extends FunctionType { super(indexInfo, "Subtract", args); } - getValue = () => this.args[0].getValue() - this.args[1].getValue(); + getValue = (dependencyValueMap: Map | undefined>) => { + try { + const resolvedValues = resolveValuesOnlyNumbers(this.args.map(arg => arg.getValue(dependencyValueMap))) + return ValueResolvingResult.success(resolvedValues[0] - resolvedValues[1]); + } catch (e) { + return ValueResolvingResult.error(e as Error) + } + } protected validateArgs: FunctionArgsValidator = (validator: PipelineValidator, args: IFunctionArg[], onSuccess: () => void, onFailure: (_: ValidationError[]) => void) => { diff --git a/src/grammar/functions/util/index.ts b/src/grammar/functions/util/index.ts new file mode 100644 index 0000000..16a000a --- /dev/null +++ b/src/grammar/functions/util/index.ts @@ -0,0 +1,17 @@ +import { ValueResolvingResult } from "../../definitions" + +export const resolveValuesOnlyNumbers = (values: ValueResolvingResult[]) => { + + const anyError = values.find(val => val.isError) + + if (anyError) { + throw anyError.getError() + } + + const resolvedValues = values.map(result => result.get()) + if (resolvedValues.some(val => !Number.isFinite(val))) { + throw new Error("Arguments must be numbers.") + } + + return resolvedValues +} \ No newline at end of file diff --git a/tst/grammar/bettermath.test.ts b/tst/grammar/bettermath.test.ts index 8d1e894..32cfb1b 100644 --- a/tst/grammar/bettermath.test.ts +++ b/tst/grammar/bettermath.test.ts @@ -1,7 +1,7 @@ import { inspect } from "util"; import type P from "parsimmon"; -import { IExpressionType, NumberType, StringType } from "../../src/grammar/definitions"; +import { IExpressionType, NumberType, RefType, StringType, ValueResolvingResult } from "../../src/grammar/definitions"; import { ConcatFunction, NegateFunction, @@ -32,43 +32,64 @@ describe("Grammar", () => { test("should parse number", () => { const expectedNumber = new NumberType(INDEX_INFO, "12"); - expect(grammar.tryParse("12").getValue()).toBe( - expectedNumber.getValue(), + expect(grammar.tryParse("12").getValue(new Map()).get()).toBe( + expectedNumber.getValue().get(), ); }); test("should parse negative number", () => { const expectedNumber = new NumberType(INDEX_INFO, "-12"); - expect(grammar.tryParse("-12").getValue()).toBe( - expectedNumber.getValue(), + expect(grammar.tryParse("-12").getValue(new Map()).get()).toBe( + expectedNumber.getValue().get(), ); }); test("should parse number after EQUALS", () => { const expectedNumber = new NumberType(INDEX_INFO, "12"); - expect(grammar.tryParse("=12").getValue()).toBe( - expectedNumber.getValue(), + expect(grammar.tryParse("=12").getValue(new Map()).get()).toBe( + expectedNumber.getValue().get(), ); }); test("should parse string", () => { const expectedString = new StringType(INDEX_INFO, "1+1"); - expect(grammar.tryParse("1+1").getValue()).toBe( - expectedString.getValue(), + expect(grammar.tryParse("1+1").getValue(new Map()).get()).toBe( + expectedString.getValue().get(), ); }); test("should parse string after EQUALS", () => { const expectedString = new StringType(INDEX_INFO, "asd"); - expect(grammar.tryParse('="asd"').getValue()).toBe( - expectedString.getValue(), + expect(grammar.tryParse('="asd"').getValue(new Map()).get()).toBe( + expectedString.getValue().get(), + ); + }); + + test("should parse ref after EQUALS", () => { + const refId = "R1"; + const refElem = new NumberType(INDEX_INFO, "11"); + const dependencyValueMap = new Map([ + [refId, refElem] + ]); + + expect(grammar.tryParse(`=${refId}`).getValue(dependencyValueMap).get()).toBe( + refElem.getValue().get() + ); + }); + + test("should parse string after EQUALS", () => { + const expectedString = new StringType(INDEX_INFO, "asd"); + + expect(grammar.tryParse('="asd"').getValue(new Map()).get()).toBe( + expectedString.getValue().get(), ); }); }); + describe("Expression values", () => { describe("Custom Functions", () => { test("should parse function with number arguments", () => { @@ -81,9 +102,9 @@ describe("Grammar", () => { "=CONCAT(12, 12)", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([12, 12]); + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([12, 12]); - expect(parsingResult.getValue()).toBe(expectedExpression.getValue()); + expect(parsingResult.getValue(new Map()).get()).toBe(expectedExpression.getValue(new Map()).get()); }); test("should parse function with string arguments", () => { @@ -96,9 +117,9 @@ describe("Grammar", () => { '=CONCAT("12", "12")', ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual(["12", "12"]); + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual(["12", "12"]); - expect(parsingResult.getValue()).toStrictEqual(expectedExpression.getValue()); + expect(parsingResult.getValue(new Map()).get()).toStrictEqual(expectedExpression.getValue(new Map()).get()); }); test("should parse function with number and string arguments", () => { @@ -111,9 +132,9 @@ describe("Grammar", () => { '=CONCAT(12, "12")', ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([12, "12"]); + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([12, "12"]); - expect(parsingResult.getValue()).toStrictEqual(expectedExpression.getValue()); + expect(parsingResult.getValue(new Map()).get()).toStrictEqual(expectedExpression.getValue(new Map()).get()); }); test("should error when string argument has too many quotes", () => { @@ -141,9 +162,9 @@ describe("Grammar", () => { '=CONCAT(12, "\\"12")', ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([12, '"12']); + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([12, '"12']); - expect(parsingResult.getValue()).toStrictEqual(expectedExpression.getValue()); + expect(parsingResult.getValue(new Map()).get()).toStrictEqual(expectedExpression.getValue(new Map()).get()); }); test("should parse string with multiple quote levels", () => { @@ -156,9 +177,9 @@ describe("Grammar", () => { '=CONCAT(12, "\\"\\"12\\"\\"")', ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([12, '""12""']); + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([12, '""12""']); - expect(parsingResult.getValue()).toStrictEqual(expectedExpression.getValue()); + expect(parsingResult.getValue(new Map()).get()).toStrictEqual(expectedExpression.getValue(new Map()).get()); }); test("should parse string with escapes of escapes", () => { @@ -171,9 +192,9 @@ describe("Grammar", () => { '=CONCAT(12, "\\"12")', ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([12, '"12']); + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([12, '"12']); - expect(parsingResult.getValue()).toStrictEqual(expectedExpression.getValue()); + expect(parsingResult.getValue(new Map()).get()).toStrictEqual(expectedExpression.getValue(new Map()).get()); }); test("should fail parsing of invalid function", () => { @@ -203,10 +224,10 @@ describe("Grammar", () => { "=-12", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([12]); + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([12]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); @@ -220,10 +241,10 @@ describe("Grammar", () => { "=11-12", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([11, 12]); + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([11, 12]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); @@ -240,10 +261,10 @@ describe("Grammar", () => { "=11-12-13", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([-1, 13]); + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([-1, 13]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); @@ -258,10 +279,10 @@ describe("Grammar", () => { "=11+12", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([11, 12]); + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([11, 12]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); @@ -278,10 +299,10 @@ describe("Grammar", () => { "=11+12+13", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([23, 13]); + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([23, 13]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); }); @@ -297,10 +318,10 @@ describe("Grammar", () => { "=11*12", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([11, 12]); + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([11, 12]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); @@ -317,12 +338,12 @@ describe("Grammar", () => { "=11*12*13", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([ + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([ 132, 13, ]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); }); @@ -338,10 +359,10 @@ describe("Grammar", () => { "=11/12", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([11, 12]); + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([11, 12]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); @@ -358,13 +379,13 @@ describe("Grammar", () => { "=11/12/13", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([ + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([ 11 / 12, 13, ]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); }); @@ -380,10 +401,10 @@ describe("Grammar", () => { "=2^3", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([2, 3]); + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([2, 3]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); test("should parse sequential Power", () => { @@ -399,13 +420,13 @@ describe("Grammar", () => { "=2^3^4", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([ + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([ 2, Math.pow(3, 4), ]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); }); @@ -420,10 +441,10 @@ describe("Grammar", () => { "=2!", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([2]); + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([2]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); @@ -438,10 +459,10 @@ describe("Grammar", () => { "=3!!", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([6]); + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([6]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); }); @@ -456,10 +477,10 @@ describe("Grammar", () => { "=-11!", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([-11]); + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([-11]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); @@ -475,12 +496,12 @@ describe("Grammar", () => { "=11^12!", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([ + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([ 11, 479001600, ]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); @@ -497,10 +518,10 @@ describe("Grammar", () => { "=11*2^3", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([11, 8]); + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([11, 8]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); @@ -517,10 +538,10 @@ describe("Grammar", () => { "=11/2^3", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([11, 8]); + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([11, 8]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); @@ -537,12 +558,12 @@ describe("Grammar", () => { "=11+12*13", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([ + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([ 11, 156, ]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); @@ -559,12 +580,12 @@ describe("Grammar", () => { "=11-12*13", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([ + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([ 11, 156, ]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); @@ -581,13 +602,13 @@ describe("Grammar", () => { "=11+12/13", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([ + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([ 11, 12 / 13, ]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); @@ -604,13 +625,13 @@ describe("Grammar", () => { "=11-12/13", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([ + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([ 11, 12 / 13, ]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); @@ -627,12 +648,12 @@ describe("Grammar", () => { "=11*12/13", ) as IFunction; - expect(parsingResult1.args.map(arg => arg.getValue())).toEqual([ + expect(parsingResult1.args.map(arg => arg.getValue(new Map()).get())).toEqual([ 132, 13, ]); - expect(parsingResult1.getValue()).toStrictEqual( - expectedExpression1.getValue(), + expect(parsingResult1.getValue(new Map()).get()).toStrictEqual( + expectedExpression1.getValue(new Map()).get(), ); const expectedExpression2 = new MultiplyFunction(INDEX_INFO, [ @@ -647,13 +668,13 @@ describe("Grammar", () => { "=11/12*13", ) as IFunction; - expect(parsingResult2.args.map(arg => arg.getValue())).toEqual([ + expect(parsingResult2.args.map(arg => arg.getValue(new Map()).get())).toEqual([ 11 / 12, 13, ]); - expect(parsingResult2.getValue()).toStrictEqual( - expectedExpression2.getValue(), + expect(parsingResult2.getValue(new Map()).get()).toStrictEqual( + expectedExpression2.getValue(new Map()).get(), ); }); @@ -670,12 +691,12 @@ describe("Grammar", () => { "=11+12-13", ) as IFunction; - expect(parsingResult1.args.map(arg => arg.getValue())).toEqual([ + expect(parsingResult1.args.map(arg => arg.getValue(new Map()).get())).toEqual([ 23, 13, ]); - expect(parsingResult1.getValue()).toStrictEqual( - expectedExpression1.getValue(), + expect(parsingResult1.getValue(new Map()).get()).toStrictEqual( + expectedExpression1.getValue(new Map()).get(), ); const expectedExpression2 = new AddFunction(INDEX_INFO, [ @@ -690,12 +711,12 @@ describe("Grammar", () => { "=11-12+13", ) as IFunction; - expect(parsingResult2.args.map(arg => arg.getValue())).toEqual([ + expect(parsingResult2.args.map(arg => arg.getValue(new Map()).get())).toEqual([ -1, 13, ]); - expect(parsingResult2.getValue()).toStrictEqual( - expectedExpression2.getValue(), + expect(parsingResult2.getValue(new Map()).get()).toStrictEqual( + expectedExpression2.getValue(new Map()).get(), ); }); @@ -711,10 +732,10 @@ describe("Grammar", () => { "=-(11+12)", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([23]); + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([23]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); @@ -731,10 +752,10 @@ describe("Grammar", () => { "=11-SUM(12,13)", ) as IFunction; - expect(parsingResult.args.map(arg => arg.getValue())).toEqual([11, 25]); + expect(parsingResult.args.map(arg => arg.getValue(new Map()).get())).toEqual([11, 25]); - expect(parsingResult.getValue()).toStrictEqual( - expectedExpression.getValue(), + expect(parsingResult.getValue(new Map()).get()).toStrictEqual( + expectedExpression.getValue(new Map()).get(), ); }); @@ -787,10 +808,39 @@ describe("Grammar", () => { expect( grammar .tryParse("=2^-4/6+11-SUM(3!,-13)+(2-2)*3!+(-2*-3)!") - .getValue(), - ).toStrictEqual(expectedExpression.getValue()); + .getValue(new Map()).get(), + ).toStrictEqual(expectedExpression.getValue(new Map()).get()); }); }); + + describe("Expressions with Refs", () => { + test("should resolve ref value within a math expression", () => { + const refs = { + R1: new NumberType(INDEX_INFO, "1"), + R2: new NumberType(INDEX_INFO, "2"), + }; + const dependencyValueMap = new Map(Object.entries(refs)); + + const expectedExpression = new AddFunction(INDEX_INFO, Object.values(refs)); + + expect(grammar.tryParse("=R1+R2").getValue(dependencyValueMap).get()).toBe( + expectedExpression.getValue(dependencyValueMap).get() + ); + }); + + test("should resolve ref value within an unary math expression", () => { + const refs = { + R1: new NumberType(INDEX_INFO, "1"), + }; + const dependencyValueMap = new Map(Object.entries(refs)); + + const expectedExpression = new NegateFunction(INDEX_INFO, Object.values(refs)); + + expect(grammar.tryParse("=-R1").getValue(dependencyValueMap).get()).toBe( + expectedExpression.getValue(dependencyValueMap).get() + ); + }); + }) }); }); }); @@ -808,6 +858,12 @@ describe("Grammar", () => { expect(validate(ast)).toStrictEqual(makeSuccess()); }); + + test("should return success on validation with refs", () => { + const ast = grammar.parse("=1+XZ765+(1-A54)") + + expect(validate(ast)).toStrictEqual(makeSuccess()); + }); describe('Add function', () => { test('should return success when arguments are numbers', () => { @@ -1177,7 +1233,7 @@ describe("Grammar", () => { }])); }) - test('should return failure when arguments are not numbers', () => { + test('should return failure when argument is not number', () => { const ast = grammar.parse('=NEGATE("1")') expect(validate(ast)).toStrictEqual(makeSemanticFailure([{ @@ -1196,6 +1252,12 @@ describe("Grammar", () => { expect(validate(ast)).toStrictEqual(makeSuccess()); }) + test('should return success when argument is Ref', () => { + const ast = grammar.parse('=NEGATE(R1)') + + expect(validate(ast)).toStrictEqual(makeSuccess()); + }) + test('should return failure when arguments are result of inner functions with incorrect type', () => { const ast = grammar.parse('=NEGATE(CONCAT("a", "b"))') @@ -1210,4 +1272,17 @@ describe("Grammar", () => { }) }) }) + + describe("Runtime value resolution", () => { + test("should return an error when resolving an invalid ref value within a math expression", () => { + const refs = { + R1: new NumberType(INDEX_INFO, "somestring"), + }; + const dependencyValueMap = new Map(Object.entries(refs)); + + expect(grammar.tryParse("=-R1").getValue(dependencyValueMap).getError()).toStrictEqual( + ValueResolvingResult.error(new Error("Arguments must be numbers.")).getError() + ); + }); + }) });