Let's write the EVL() function for X#...

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

Anonymous

Let's write the EVL() function for X#...

Post by Anonymous »

Hey all you VFP devs - Let's help the X# Dev Team spend their time working on really important stuff, while we write a few missing Runtime functions. I'm not expert at this X# stuff yet, but it doesn't have our beloved EVL() function, so I started poking away at one that seems to work well so far. If we get something that works in our testing, maybe X# Dev Team will include it in one of the upcoming releases.

I've two things so far: an actual EVL() function, and another TextEVL() function which makes multiple calls to EVL() with various data types. I've only hit the Int, String, and Logical data types so far, but maybe someone else can join me in getting this completes. Let's see what we can do!

Code: Select all

// Early development of FoxPro EVL() Function by Matt Slay.
// Dear community - please help my finish this with any missing data types or
// even determine if there is a better way to implement this Function for X# Runtime.

// Discuss on this X# forum thread: https://www.xsharp.eu/forum/public-vfp/1923-let-s-write-the-evl-function-for-x

Function Evl(uValue, uReturnValue)
	
	Var dataType = ValType(uValue) // Using native X# function call here.
	
	Do Case
	    Case dataType = "C"
		    If Alltrim(uValue) == ""
			    Return uReturnValue
		    Else
			    Return uValue
			Endif
		Case dataType = "N"
			If uValue = 0
				Return uReturnValue
			Else
				Return uValue
			Endif
		Case dataType = "L"
			If uValue
				Return .t.
			Else
				Return uReturnValue
			EndIf
	End Case
	
End Function

Test function:

Code: Select all

*-- This function tests the  EVL() function
Function TestEvl() As Logic
	
	Local varIntZero, varIntNonZero
	Local varDecimalZero, varDecimalNonZero
	Local varEmptyString, VarNotEmptyString
	Local varDate
	Local varDateTime
	Local varObject
	Local varNull
	Local lPassed
	
	lPassed = .t.
	
	varIntZero = 0
	VarIntNonZero = 1

	// Integer 0
	If Evl(varIntZero, 1) = 1
		? "Evl() on integer Zero Passed."
	Else
		? "Evl() on a integer 0."
		lPassed = .f.
	Endif
	
	// Non-zer Integer
	If Evl(varIntNonZero, 0) = 1 
		? "Evl() on a Non-zero Integer Passed."
	Else
		? "Evl() on a Non-zero Integer FAILED!!!"
		lPassed = .f.
	Endif
	
	// Empty String
	If Evl("", 1) = 1
		? "Evl() on empty String Passed."
	Else
		? "Evl() on empty String FAILED !!!"
		lPassed = .f.
	Endif
	
	// Non-Empty String
	If Evl("Not_Empty_String", 1) = "Not_Empty_String"
		? "Evl() on Non-Empty String Passed."
	Else
		? "Evl() on empty String FAILED !!!"
		lPassed = .f.
	Endif

	// Logical False
	If Evl(.f., 1) = 1
		? "Evl() on logical .F. Passed."
	Else
		? "Evl() on logical .F. FAILED !!!"
		lPassed = .f.
	Endif
		
	// Logical True
	If Evl(.t., 1) = .t.
		? "Evl() on logical .T. Passed."
	Else
		? "Evl() on logical .T. FAILED !!!"
		lPassed = .f.
	Endif
	
	Var oObject = Empty{}
	AddProperty(oObject, "TestProperty", 1)
	// Test by passing a dynamically added property on Empty object
	If Evl(oObject.TestProperty, 2) = 1
		? "Evl() on Dynamically added property Passed."
	Else
		? "Evl() on Dynamically added property Failed !!!"
		lPassed = .f.
	Endif
	

	// ToDo: Test Date & Empty Date
	// Todo: Test Null
	// ToDo: Test DateTime & Empty DateTime 
	// ToDo: Figure out all the other data types that need to be tested.
	
	Wait
	
	Return lPassed
	
End Function
2020-05-07 18_48_40-VPF Xsharp test app 1 (Running) - Microsoft Visual Studio.png
2020-05-07 18_48_40-VPF Xsharp test app 1 (Running) - Microsoft Visual Studio.png (27.79 KiB) Viewed 370 times
mainhatten
Posts: 200
Joined: Wed Oct 09, 2019 6:51 pm

Let's write the EVL() function for X#...

Post by mainhatten »

Hi Matt,
not trying to be philosophical, but I am uncertain if your approach is best for xSharp.

If one aims for code reading a lot like normal xBase for all supported data types, I'd implement Evl() for each datatype combination given as parameter in a separate 1-liner function, resulting in a plethora of simple checks and returns. Both empty and evl() were THAT helpful in vfp because we did not have static (known) types to check directly against, for perf reason sidestepping the function call. For usual datatype alone you probably could shorten to iif(empty(uValue), uReturmValue, uValue).

IAC when comparing against same data type, switch is better in xSharp, as it does not compile into if-elsif structure needing check at every branch but can directly jump to correct branch like switch in C.

my 0.02€
thomas
User avatar
robert
Posts: 4225
Joined: Fri Aug 21, 2015 10:57 am
Location: Netherlands

Let's write the EVL() function for X#...

Post by robert »

Thomas,
In principle you are right. However I suspect 99% of the code that calls EVL() will have an expression with the first type USUAL. So the only logical thing would be to overload on the 2nd parameter (the default value):

Code: Select all

FUNCTION EVL(eExpression1 AS USUAL, eExpression2 AS LONG) AS USUAL
FUNCTION EVL(eExpression1 AS USUAL, eExpression2 AS STRING) AS USUAL
FUNCTION EVL(eExpression1 AS USUAL, eExpression2 AS DATE) AS USUAL
FUNCTION EVL(eExpression1 AS USUAL, eExpression2 AS FLOAT) AS USUAL
FUNCTION EVL(eExpression1 AS USUAL, eExpression2 AS CURRENCY) AS USUAL
FUNCTION EVL(eExpression1 AS USUAL, eExpression2 AS OBJECT) AS USUAL
etc

And then each function would look like

Code: Select all

IF IsEmpty(eExpression1 )
   RETURN eExpression2 
ENDIF
RETURN eExpression1 
Is this what you meant ?
I don't see an advantage over this

Code: Select all

FUNCTION EVL(eExpression1 AS USUAL, eExpression2 AS USUAL) AS USUAL
IF IsEmpty(eExpression1 )
   RETURN eExpression2 
ENDIF
RETURN eExpression1 
Btw I noticed that VFP allows this:

Code: Select all

? EVL("abcde", 1234)

and returns the "abcde". So the type of the 2 expressions does not have to be the same
This returns 1234

Code: Select all

? EVL("        ", 1234)


But this

Code: Select all

 EVL(.NULL., 1234)

returns .NULL. ???

Robert
XSharp Development Team
The Netherlands
robert@xsharp.eu
mainhatten
Posts: 200
Joined: Wed Oct 09, 2019 6:51 pm

Let's write the EVL() function for X#...

Post by mainhatten »

robert wrote:

Code: Select all

 EVL(.NULL., 1234)

returns .NULL. ???
Hi Robert,
that one is ok, as .null. is not considered empty and you have the explicit isnull() checking function plus parallel nvl() to evl()

Code: Select all

  ? EMPTY(.null.)
on the rest more later - I don't have firm conviction yet, just some hunches that this will be an area where xSharp and vfp best practice code will be different. For instance for string var/parameter checking with empty function IMO makes sense, not so on boolean or numeric variables.

Similar the need for different overloaded functions in xSharp for "optional ref parameter" eliminates some of the evl/nvl need necessary in vfp code.

OTOH just recompiling vfp code and running should be possible, with the option to enhance xSharp later to use benefits of static typing.

regards
thomas
atlopes
Posts: 83
Joined: Sat Sep 07, 2019 11:43 am

Let's write the EVL() function for X#...

Post by atlopes »

Robert asked

But this

Code: Select all

EVL(.NULL., 1234)
returns .NULL. ???
In VFP, in general, any function that accepts a .NULL. for one of its arguments returns .NULL. as a result.

For instance

Code: Select all

MAX(1, .NULL.)
ABS(.NULL.)
LEN(.NULL.)
FV(.NULL., 1, 2)
FV(100, .NULL., 2)
FV(100, 1, .NULL.)
and so on.

Obvious exceptions are NVL(), ISNULL(), and VARTYPE().
atlopes
Posts: 83
Joined: Sat Sep 07, 2019 11:43 am

Let's write the EVL() function for X#...

Post by atlopes »

Matt

The ALLTRIM() to test for the emptiness of character expressions must comprehend CHR(9), CHR(10), and CHR(13) also.

That is

Code: Select all

IF ALLTRIM(uValue, 0, " ", CHR(9), CHR(13), CHR(10)) == "" 
User avatar
robert
Posts: 4225
Joined: Fri Aug 21, 2015 10:57 am
Location: Netherlands

Let's write the EVL() function for X#...

Post by robert »

Antonio,

A minor thing maybe, but I don't think an empty check should call AllTrim() (since that creates a new string). It should be sufficient to walk the list of characters and check for empty / not empty characters.

Imagine running this check on a string of 64K characters, If you walk the list of chars you can most likely abort the loop because you find a non empty char in the first few characters.

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

Let's write the EVL() function for X#...

Post by atlopes »

Robert

I don't know how ALLTRIM() is implemented but you're most probably right. ALLTRIM() could be inefficient for large character expressions, and even for smaller ones. But walking through the 1 to LEN(string) positions looking for something outside CHR(32) + CHR(9) + CHR(10) + CHR(13) wouldn't also require constant substring slicing?
User avatar
robert
Posts: 4225
Joined: Fri Aug 21, 2015 10:57 am
Location: Netherlands

Let's write the EVL() function for X#...

Post by robert »

Antonio,
The string type in .Net has a Chars property which is an indexable collection of characters in the string. No allocation is needed as far as I know to address individual characters .
The string class is written in CPP:
You can see here that it simply indexes the character in the buffer
https://github.com/dotnet/coreclr/blob/ ... ve.cpp#L52

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

Let's write the EVL() function for X#...

Post by atlopes »

Robert,

That seems much more memory efficient. Something for Matt to consider, I'm sure.
Post Reply