Implementing missing VFP functions

This forum is meant for questions about the Visual FoxPro Language support in X#.

User avatar
Chris
Posts: 4907
Joined: Thu Oct 08, 2015 7:48 am
Location: Greece

Implementing missing VFP functions

Post by Chris »

Hi Antonio,
atlopes wrote:Robert,

I ran the following code against the exact implementation of the BITNOT() functions above.

Code: Select all

LOCAL N
LOCAL B

N := 10
B := 0hFFB0

? BITNOT(N)
? BITNOT(B)
The first display statement executes properly and calls the BITNOT(Int) AS Int, the second raises an error: 'XSharp.Error: 'Conversion.Error from USUAL(UNKNOWN) to LONGINT'.

My question is: why is X# trying to convert a USUAL to LONGINT in the first place? If X# knows the type of the underlying value in the second statement ("Q"), and if it's going to convert before calling the function, why not convert to Binary instead?
This happens because the decision about which overload to call happens at compile time, and at that point the compiler does not know which of the two to use, because it does not know what "B" holds, this is determined at runtime.

For both cases it chooses to use the INT overload, although this looks like a bug, the correct behavior would be to report an "ambiguous function call" error, since none of the two functions is more suitable at runtime. This is why Robert suggested to use one "public" interface to the function, because otherwise the compiler will not know which one to use.
Chris Pyrgas

XSharp Development Team
chris(at)xsharp.eu
atlopes
Posts: 83
Joined: Sat Sep 07, 2019 11:43 am
Location: Portugal

Implementing missing VFP functions

Post by atlopes »

Ok, Chris, this was what I needed to know. We must not rely on X#'s decision to convert the USUAL value to a LONGINT, and you'll probably address the issue in the future.

This implementation of BITNOT() now takes USUAL into consideration:

Code: Select all

FUNCTION BITNOT (Arg1 AS USUAL) AS USUAL

    IF VARTYPE(Arg1) == "Q"
        RETURN BITNOT((Binary)Arg1)
    ELSE
        RETURN BITNOT((Int)Arg1)
    ENDIF

ENDFUNC

FUNCTION BITNOT (Arg1 AS USUAL, Arg2 AS USUAL, Arg3 := 1 AS USUAL) AS Binary

    IF VARTYPE(Arg1) == "Q"
        RETURN BITNOT((Binary)Arg1, (Int)Arg2, (Int)Arg3)
    ELSE
        THROW ArgumentException {}
    ENDIF

ENDFUNC

FUNCTION BITNOT (Num AS Int) AS Int

    RETURN ~Num

ENDFUNC

FUNCTION BITNOT (BinString AS Binary) AS Binary

    RETURN BITNOT(BinString, 0, BinString.Length * 8)

ENDFUNC

FUNCTION BITNOT (BinString AS Binary, StartBit AS Int, BitCount := 1 AS Int) AS Binary

    LOCAL Result := 0h + BinString AS Byte[]
    LOCAL ByteIndex AS Int
    LOCAL BitIndex := StartBit AS Int
    LOCAL BitCounter AS Int

    FOR BitCounter := 1 TO BitCount
        
        ByteIndex := BitIndex / 8 + 1
        IF ByteIndex >= 1 AND ByteIndex <= Result.Length
            Result[ByteIndex] := _Xor(Result[ByteIndex], 1 << BitIndex % 8)
            BitIndex++
        ELSE
            THROW ArgumentException {}
        ENDIF

    NEXT

    RETURN (Binary)Result

ENDFUNC
While testing, I noticed another problem with the Binary type. Probably due to its underlying nature, as a function parameter it's being passed by reference instead of value.

Hence, the need for the initialization of the Result variable in BITNOT() function to explicitly use a new value

Code: Select all

LOCAL Result := 0h + BinString AS Byte[]
instead of simply

Code: Select all

LOCAL Result := BinString AS Byte[]
This will certainly pose more problems elsewhere.

Without this tweaking, this snippet

Code: Select all

LOCAL B

B := 0hFFB0

? B
? BITNOT(B)
? B
results in

Code: Select all

0hFFB0
0h004F
0h004F
instead of

Code: Select all

0hFFB0
0h004F
0hFFB0
as it should.
Karl-Heinz
Posts: 774
Joined: Wed May 17, 2017 8:50 am
Location: Germany

Implementing missing VFP functions

Post by Karl-Heinz »

Hi Antonio,

i modified the usual code from Robert and it gives the same results. Now it doesn´t matter how the var 'B' is declared.

PRIVATE B
LOCAL B
LOCAL B AS BINARY

Code: Select all

FUNCTION myBITNOT ( num AS USUAL ) AS USUAL 
	
SWITCH UsualType(Num)
	
CASE __UsualType.Long
    RETURN ~ (LONG) Num // it seems the cast is not really needed
     
CASE __UsualType.Binary
   RETURN myBitNot( (BINARY) Num , 0 , ((BINARY)( num)):Length * 8 ) 
   
OTHERWISE
   THROW Exception{"Unexpected type" } 
   
END SWITCH	

FUNCTION myBitNOT ( BinString AS USUAL , StartBit AS INT, BitCount := 1 AS INT) AS BINARY

	IF UsualType( BinString ) == __UsualType.Binary
			
		LOCAL Result := (BINARY) BinString AS BYTE[]
		LOCAL ByteIndex AS INT
		LOCAL BitIndex := StartBit AS INT
		LOCAL BitCounter AS INT

		FOR BitCounter := 1 TO BitCount
        
			ByteIndex := BitIndex / 8 + 1
			Result[ByteIndex] := _Xor(Result[ByteIndex], 1 << BitIndex % 8)
			BitIndex++

		NEXT 
    	
		RETURN (Binary)Result
		    
	ELSE 
		
		THROW Exception{"Unexpected type" }			

	ENDIF 
regards
Karl-Heinz
User avatar
Chris
Posts: 4907
Joined: Thu Oct 08, 2015 7:48 am
Location: Greece

Implementing missing VFP functions

Post by Chris »

Hi Anotinio,

I did not say that there is a problem converting a USUAL to INT, this works fine. But of course, if the USUAL does not contain a numeric value, then a conversion to INT will throw an error as expected.

The problem is that when you compile the code, the compiler does not know which one of the many functions to choose, and there is no way to solve that, at least none I can think of, when you use multiple overloads.

Since you now added also a USUAL overload, then the compiler will be choosing this one always, when the argument is untyped, so you can also keep the previous BINARY overload, this will not be called accidentally anymore. I am just not sure if the fact the the compiler currently does pick the USUAL overload among other overloads when passing a USUAL to it should be considered a language feature or "working by coincidence" (that could change in the future), maybe Robert can comment on that.

If this is supposed to always work like that, then I guess it's no harm keeping the non-usual overloads. For existing FoxPro code (where everything is untyped anyway), always the USUAL overload will be called. But for new code, it is indeed handy to be able to call a more suitable overload.
Chris Pyrgas

XSharp Development Team
chris(at)xsharp.eu
atlopes
Posts: 83
Joined: Sat Sep 07, 2019 11:43 am
Location: Portugal

Implementing missing VFP functions

Post by atlopes »

Chris
I did not say that there is a problem converting a USUAL to INT, this works fine.
Nor did I, I hope. Just to clarify, what I questioned was the decision to perform that conversion in particular over others that could be possible. I was ok with you saying
this looks like a bug, the correct behavior would be to report an "ambiguous function call" error, since none of the two functions is more suitable at runtime
I'd prefer to have and keep the overloads for typed arguments. Not only this would reward future code, but it also rewards current VFP typed source code (there is such a thing!).

A final note: I could not reproduce the by reference vs. by value point I mentioned above outside the implementation of BITNOT(). Nevertheless, I think that the behavior is verifiable. Hopefully, it's just something I'm overlooking.
atlopes
Posts: 83
Joined: Sat Sep 07, 2019 11:43 am
Location: Portugal

Implementing missing VFP functions

Post by atlopes »

Karl-Heinz,
i modified the usual code from Robert and it gives the same results. Now it doesn´t matter how the var 'B' is declared
As I said to Chris, I would prefer to have all overloads in place. That would reward properly typed code. It would also benefit from the implicit type conversions that the compiler can normally perform, for instance, from float to integer. Your approach requires looking for other numeric types that a USUAL can host, doesn't it?
Karl-Heinz
Posts: 774
Joined: Wed May 17, 2017 8:50 am
Location: Germany

Implementing missing VFP functions

Post by Karl-Heinz »

Hi Antonio,
As I said to Chris, I would prefer to have all overloads in place. That would reward properly typed code. It would also benefit from the implicit type conversions that the compiler can normally perform, for instance, from float to integer. Your approach requires looking for other numeric types that a USUAL can host, doesn't it?
search for "UsualType" in the XSharp help and open the item "UsualType Enumeration". There you´ll find the description of all enum members.

regards
Karl-Heinz
User avatar
Chris
Posts: 4907
Joined: Thu Oct 08, 2015 7:48 am
Location: Greece

Implementing missing VFP functions

Post by Chris »

Hi Antonio,
atlopes wrote: Just to clarify, what I questioned was the decision to perform that conversion in particular over others that could be possible.
Understood, but the conversion is not a problem, the problem is that the compiler could not know which overload to call. When you had only those two typed overloads:

FUNCTION BITNOT (n AS INT)
FUNCTION BITNOT (b AS Binary)

and you called it with

BITNOT(any_untyped_var)

then the compiler does not know which one of the two to use. Currently it uses the first one, although there is not really a reason to assume that this one is the intended one (or not). Assuming that the compiler did the right thing by using one of those overloads, then it is also correct to emit code that converts the untyped var value into an INT (the type expected from the function). The conversion is correct, what is wrong is that the compiler did pick one of the two functions, instead of stopping compilation and throwing a compiling error message instead.

atlopes wrote: I'd prefer to have and keep the overloads for typed arguments. Not only this would reward future code, but it also rewards current VFP typed source code (there is such a thing!).
Do you mean typing a LOCAL with "AS"? From what I see, in VFP you can do

LOCAL n AS Integer
n := "Let's put a string in an Integer variable :-)"
? n

and that runs with no errors, so it is not typed really. Or do you mean with a different way?

atlopes wrote: A final note: I could not reproduce the by reference vs. by value point I mentioned above outside the implementation of BITNOT(). Nevertheless, I think that the behavior is verifiable. Hopefully, it's just something I'm overlooking.
No you're right, there's an issue here. The Binary type itself is a STRUCTURE (so it's being passed by value), but it holds its data internally in a byte array (so a reference type) and when you convert the Binary value into a BYTE array in your first local assignment, then the conversion returns that exact internal array. Maybe the best way is to return a new copy of the array instead, will log an incident on that to be looked at.

For now, you can change such code to

LOCAL Source := BinString AS BYTE[]
LOCAL Result := BYTE[]{Source:Length} AS BYTE[]
System.Array.Copy(Source, Result, Source:Length)

although it probably makes more sens to do this same thing in the X# runtime instead.
Chris Pyrgas

XSharp Development Team
chris(at)xsharp.eu
User avatar
robert
Posts: 4520
Joined: Fri Aug 21, 2015 10:57 am
Location: Netherlands

Implementing missing VFP functions

Post by robert »

Chris,
Chris wrote:
No you're right, there's an issue here. The Binary type itself is a STRUCTURE (so it's being passed by value), but it holds its data internally in a byte array (so a reference type) and when you convert the Binary value into a BYTE array in your first local assignment, then the conversion returns that exact internal array. Maybe the best way is to return a new copy of the array instead, will log an incident on that to be looked at.
Given the fact that the binary type is a value type it indeed makes sense to return a copy of the byte array as well (in other words, to make it a really immutable type).

Robert
XSharp Development Team
The Netherlands
robert@xsharp.eu
atlopes
Posts: 83
Joined: Sat Sep 07, 2019 11:43 am
Location: Portugal

Implementing missing VFP functions

Post by atlopes »

Chris,
The conversion is correct, what is wrong is that the compiler did pick one of the two functions, instead of stopping compilation and throwing a compiling error message instead.
Exactly this.
[...] so it is not typed really. Or do you mean with a different way?
What is typed is the source code, even if not enforced by the interpreter. A VFP programmer can do almost anything with the variables that he/she declares, including not declaring them at all, but if he/she declares the variables with their types it's certainly not with the intention of trashing the effort.

As for Binary values passed as parameters, thank you for confirming and for your suggestion. Adding an empty 0h to it creates a local copy that can be safely processed.
Post Reply