{ Apophysis AV "Phoenix Edition" Copyright (C) 2021-2022 Alice V. Koryagina } unit MathExpressions; interface uses System.Rtti, System.SysUtils, System.Bindings.EvalProtocol, System.Bindings.Evaluator, System.Bindings.EvalSys, System.Bindings.Methods; function CalculateExpression(const Expr: string): string; var InDegrees: boolean; implementation uses System.Math, Windows, Translation; var LScope: IScope; function Sine: IInvokable; begin Result := MakeInvokable( function(Args: TArray): IValue var IAValue: IValue; ANum: Double; begin // AV: check the number of passed parameters if Length(Args) <> 1 then begin MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'), ['Sin()', 1, Length(Args)])), 'Apophysis AV', 48); exit; end; IAValue := Args[0]; try ANum := IAValue.GetValue.AsExtended; // AV: check the parameter type if InDegrees then // AV: translate the parameter into radians ANum := DegToRad(ANum); Exit(TValueWrapper.Create(RoundTo(sin(ANum), -6))); except MessageBox(0, PChar('Sin(): ' + TextByKey('formula-wrongdatatype')), 'Apophysis AV', 16); end; end ); end; function CoSine: IInvokable; begin Result := MakeInvokable( function(Args: TArray): IValue var IAValue: IValue; ANum: Double; begin // AV: check the number of passed parameters if Length(Args) <> 1 then begin MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'), ['Cos()', 1, Length(Args)])), 'Apophysis AV', 48); exit; end; IAValue := Args[0]; try ANum := IAValue.GetValue.AsExtended; if InDegrees then // AV: translate the parameter into radians ANum := DegToRad(ANum); Exit(TValueWrapper.Create(RoundTo(cos(ANum), -6))); except MessageBox(0, PChar('Cos(): ' + TextByKey('formula-wrongdatatype')), 'Apophysis AV', 16); end; end ); end; function ArcSine: IInvokable; begin Result := MakeInvokable( function(Args: TArray): IValue var IAValue: IValue; AValue: Double; begin // AV: check the number of passed parameters if Length(Args) <> 1 then begin MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'), ['ArcSin()', 1, Length(Args)])), 'Apophysis AV', 48); exit; end; IAValue := Args[0]; try AValue := IAValue.GetValue.AsExtended; if InRange(AValue, -1, 1) then begin AValue := arcsin(AValue); if InDegrees then AValue := RadToDeg(AValue); Exit(TValueWrapper.Create(RoundTo(AValue, -6))); end else MessageBox(0, PChar('ArcSin(): ' + TextByKey('formula-outofrange')), 'Apophysis AV', 48); except MessageBox(0, PChar('ArcSin(): ' + TextByKey('formula-wrongdatatype')), 'Apophysis AV', 16); end; end ); end; function ArcCoSine: IInvokable; begin Result := MakeInvokable( function(Args: TArray): IValue var IAValue: IValue; AValue: Double; begin //AV: check the number of passed parameters if Length(Args) <> 1 then begin MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'), ['ArcCos()', 1, Length(Args)])), 'Apophysis AV', 48); exit; end; IAValue := Args[0]; try AValue := IAValue.GetValue.AsExtended; if InRange(AValue, -1, 1) then begin AValue := arccos(AValue); if InDegrees then AValue := RadToDeg(AValue); Exit(TValueWrapper.Create(RoundTo(AValue, -6))); end else MessageBox(0, PChar('ArcCos(): ' + TextByKey('formula-outofrange')), 'Apophysis AV', 48); except MessageBox(0, PChar('ArcCos(): ' + TextByKey('formula-wrongdatatype')), 'Apophysis AV', 16); end; end ); end; function ArcTangentYX: IInvokable; begin Result := MakeInvokable( function(Args: TArray): IValue var IYValue, IXValue: IValue; AValue: Double; begin // AV: check the number of passed parameters if Length(Args) <> 2 then begin MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'), ['ArcTan2()', 2, Length(Args)])), 'Apophysis AV', 48); exit; end; IYValue := Args[0]; IXValue := Args[1]; try AValue := arctan2(IYValue.GetValue.AsExtended, IXValue.GetValue.AsExtended); if InDegrees then AValue := RadToDeg(AValue); Exit(TValueWrapper.Create(RoundTo(AValue, -6))); except MessageBox(0, PChar('ArcTan2(): ' + TextByKey('common-invalidformat')), 'Apophysis AV', 16); end; end ); end; function SquareRoot: IInvokable; begin Result := MakeInvokable( function(Args: TArray): IValue var IAValue: IValue; ANum: Double; begin // AV: check the number of passed parameters if Length(Args) <> 1 then begin MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'), ['Sqrt()', 1, Length(Args)])), 'Apophysis AV', 48); exit; end; IAValue := Args[0]; try ANum := IAValue.GetValue.AsExtended; if ANum >= 0 then Exit(TValueWrapper.Create(RoundTo(sqrt(ANum), -6))) else MessageBox(0, PChar('Sqrt(): ' + TextByKey('formula-unsigned')), 'Apophysis AV', 48); except MessageBox(0, PChar('Sqrt(): ' + TextByKey('formula-wrongdatatype')), 'Apophysis AV', 16); end; end ); end; function NatLog: IInvokable; begin Result := MakeInvokable( function(Args: TArray): IValue var IAValue: IValue; ANum: Double; begin // AV: check the number of passed parameters if Length(Args) <> 1 then begin MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'), ['Ln()', 1, Length(Args)])), 'Apophysis AV', 48); exit; end; IAValue := Args[0]; try ANum := IAValue.GetValue.AsExtended; if ANum > 0 then Exit(TValueWrapper.Create(RoundTo(ln(ANum), -6))) else MessageBox(0, PChar('Ln(): ' + TextByKey('formula-unsigned')), 'Apophysis AV', 48); except MessageBox(0, PChar('Ln(): ' + TextByKey('formula-wrongdatatype')), 'Apophysis AV', 16); end; end ); end; function PowerXY: IInvokable; begin Result := MakeInvokable( function(Args: TArray): IValue var IYValue, IXValue: IValue; ANum: Double; begin // AV: check the number of passed parameters if Length(Args) <> 2 then begin MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'), ['Power()', 2, Length(Args)])), 'Apophysis AV', 48); exit; end; IXValue := Args[0]; IYValue := Args[1]; try ANum := IXValue.GetValue.AsExtended; if ANum >= 0 then Result := TValueWrapper.Create(RoundTo(power(ANum, IYValue.GetValue.AsExtended), -6)) else MessageBox(0, PChar('Power(): ' + TextByKey('formula-unsigned')), 'Apophysis AV', 48); except MessageBox(0, PChar('Power(): ' + TextByKey('common-invalidformat')), 'Apophysis AV', 16); end; end ); end; procedure RegisterMathFunctions; begin TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create( Sine, 'sin', 'sin', '', True, '', nil)); TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create( CoSine, 'cos', 'cos', '', True, '', nil)); TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create( ArcSine, 'arcsin', 'arcsin', '', True, '', nil)); TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create( ArcCoSine, 'arccos', 'arccos', '', True, '', nil)); TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create( ArcTangentYX, 'arctan2', 'arctan2', '', True, '', nil)); TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create( SquareRoot, 'sqrt', 'sqrt', '', True, '', nil)); TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create( PowerXY, 'power', 'power', '', True, '', nil)); TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create( NatLog, 'ln', 'ln', '', True, '', nil)); LScope := BasicOperators; TDictionaryScope(LScope).Map.Add('pi', TValueWrapper.Create(pi)); TDictionaryScope(LScope).Map.Add('exp', TValueWrapper.Create(exp(1))); // AV: add the registered methods LScope := TNestedScope.Create(LScope, TBindingMethodsFactory.GetMethodScope); end; function CalculateExpression(const Expr: string): string; var LCompiledExpr : ICompiledBinding; LResult : TValue; begin Result := ''; try LCompiledExpr := Compile(Expr, LScope); LResult := LCompiledExpr.Evaluate(LScope, nil, nil).GetValue; if not LResult.IsEmpty then Result := LResult.ToString; except Result := ''; MessageBox(0, PChar(TextByKey('formula-cannotevaluate')), 'Apophysis AV', 16); end; end; /////////////////////////////////////////////////////////////////////////////// initialization RegisterMathFunctions; end.