tmteam / NFun

Expressions Evaluator for .NET
MIT License
57 stars 4 forks source link

Exception: (Expected: UInt16, but was: UInt16) #73

Open Ekul34 opened 1 year ago

Ekul34 commented 1 year ago

Under most cases when working with UInt16 and Int16 types seem to have a bug saying it didn't receive what it expected even though the exception actually says it did receive the correct type.

I thought the casting of the input value before going into the runtime would help, but it didn't. Maybe there is some way for me to cast in the script that makes this work, but I tried that and was unable to fix the issue.

If there are any workarounds I could use in the meantime, please let me know.

Results from the following scripts

DoubleToDouble(input): Output out@2:Real = 2
FloatToFloat(input):   Output out@2:Real = 2
Int32ToInt32(input):   Output out@2:Int32 = 2
UInt32ToUInt32(input): Output out@2:UInt32 = 2
Int16ToInt16(input):   Output out@2:Int16 = 2
UInt16ToUInt16(input): Output out@2:UInt16 = 2

DoubleToDouble(input + input): Output out@2:Real = 4
FloatToFloat(input + input):   Output out@2:Real = 4
Int32ToInt32(input + input):   Output out@2:Int32 = 4     
UInt32ToUInt32(input + input): Output out@2:UInt32 = 4
Int16ToInt16(input + input):   Unhandled exception. [FU783] Invalid function call argument: Int16ToInt16(Int16)->Int16. Expected: Int16, but was: Int16
UInt16ToUInt16(input + input): Unhandled exception. [FU783] Invalid function call argument: UInt16ToUInt16(UInt16)->UInt16. Expected: UInt16, but was: UInt16
public static void Main()
{
string script = "UInt16ToUInt16(input + input)";
var runtime = Funny.Hardcore
    .WithFunction(nameof(DoubleToDouble), (double number) => DoubleToDouble(number))
    .WithFunction(nameof(FloatToFloat), (float number) => FloatToFloat(number))
    .WithFunction(nameof(Int32ToInt32), (Int32 number) => Int32ToInt32(number))
    .WithFunction(nameof(UInt32ToUInt32), (UInt32 number) => UInt32ToUInt32(number))
    .WithFunction(nameof(Int16ToInt16), (Int16 number) => Int16ToInt16(number))
    .WithFunction(nameof(UInt16ToUInt16), (UInt16 number) => UInt16ToUInt16(number))
    .Build(script);

runtime["input"].Value = (UInt16)2;
      runtime.Run();
Console.WriteLine(runtime["out"]);
}
private static double DoubleToDouble(double number) => number;
private static float FloatToFloat(float number) => number;
private static Int32 Int32ToInt32(Int32 number) => number;
private static UInt32 UInt32ToUInt32(UInt32 number) => number;
private static Int16 Int16ToInt16(Int16 number) => number;
private static UInt16 UInt16ToUInt16(UInt16 number) => number;
Ekul34 commented 1 year ago

The workaround I have found so far is to just take a double for every argument and cast it straight away to whatever I actually wanted to accept. It's awful, but it works for now.

public static void Main()
{
string script = "UInt16ToUInt16(input + input)";
var runtime = Funny.Hardcore
    .WithFunction(nameof(DoubleToDouble), (double number) => DoubleToDouble(number))
    .WithFunction(nameof(FloatToFloat), (double number) => FloatToFloat(number))
    .WithFunction(nameof(Int32ToInt32), (double number) => Int32ToInt32(number))
    .WithFunction(nameof(UInt32ToUInt32), (double number) => UInt32ToUInt32(number))
    .WithFunction(nameof(Int16ToInt16), (double number) => Int16ToInt16(number))
    .WithFunction(nameof(UInt16ToUInt16), (double number) => UInt16ToUInt16(number))
    .Build(script);

runtime["input"].Value = (UInt16)2;
runtime.Run();
Console.WriteLine(runtime["out"]);
}
private static double DoubleToDouble(double number) => number;
private static float FloatToFloat(double number) => (float)number;
private static Int32 Int32ToInt32(double number) => (Int32)number;
private static UInt32 UInt32ToUInt32(double number) => (UInt32)number;
private static Int16 Int16ToInt16(double number) => (Int16)number;
private static UInt16 UInt16ToUInt16(double number) => (UInt16)number;
tmteam commented 1 year ago

Hi, thank for report. I discovered the issue and figure out that it is not a bug, but wrong error message text, and lack of diallect settings

Actual Nfun error explanation

According to borring specification operator + cannot be used for Int16 types, as it is defined only for arithmetical types (int32 int64 uint32 uint64 real). This is done to protect against an overflow error, just as it is done in C#

So original problem is that in script UInt16ToUInt16(input + input) expression input + input can have only int32 or uint32 type. But function UInt16ToUInt16 has argument of uint16. uint32cannot be converted into uint16 so you have error

The same story for C# itself. Try to execute following C# code:

       // C# code
        UInt16 a = 0;
        UInt16 b = 1;

        F(a+b);//Error: Argument type 'int' is not assignable to parameter type 'short'

        static UInt16 F(UInt16 x) => x;

Workaround

Use Int32 or Uint32 for your function paramers instead of Int16 or Uint16

Call to action

  1. To fix error message for your case
  2. To add new Dialect settings that allows overflow arithmetical operations for uint16/int16 to allow this code:
    
    a:uint16 = 1
    b:uint16 = 1

x:uint16 = a+b y:uint16 = a-b z:uint16 = a*b

Ekul34 commented 1 year ago

Thank you so much for an amazing explanation so quickly. I was unaware that c# automatically converts UInt16/Int16 types.

Due to working with Modbus devices, I will be dealing with Uint16 and Uint16[] data types a lot. These Uint16/Uint16[] types in Modbus represent the raw Modbus registers that in tern make up whatever data types the manufacturer has deemed them to be used for (they quite often enjoy making up their own new data types).

I think my solution as you said is to use a larger data type as the function parameters to allow for manipulation of these values. But in the end, they will need to be converted back to Uint16/Uint16[] after the fact to fit into Modbus registers.

Thank you again. Please consider adding a donation link to this project so those who use NFun can support you.