Skip to content

Fix #1856: Add filename parameter support and fix FSize VFP compatibi…#1861

Open
Irwin1985 wants to merge 2 commits intodevfrom
fix-issue-1856-vfp-file-functions
Open

Fix #1856: Add filename parameter support and fix FSize VFP compatibi…#1861
Irwin1985 wants to merge 2 commits intodevfrom
fix-issue-1856-vfp-file-functions

Conversation

@Irwin1985
Copy link
Contributor

Resolves #1856

  • Updated FDate() and FTime() in DateFunctions.prg to accept a filename parameter and correctly report the modification date/time of a given file. FDate() now correctly supports the nType parameter to return either a Date or Datetime value.
  • Implemented FAttrib() and FName() overloaded functions with a string filename parameter inside FileFunctions.prg.
  • Updated FSize() in RuntimeCoreWrappers.prg to correctly return the file size in bytes instead of the DBF field size when SET COMPATIBLE is ON. This implementation also handles the VFP edge case of automatically appending a .dbf extension if none is provided.
  • Added comprehensive unit tests to ensure parity with VFP behavior.

@RobertvanderHulst
Copy link
Member

Irwin,
For the compatible state you can either request RuntimeState.Compatible or use the function SetCompatible().
We store it as a logical in the runtime state, so there is no need to check for the string value.
I see you have also changed some settings in XSharp.VFP:

 <TreatWarningsAsErrors>False</TreatWarningsAsErrors>
    <DocumentationFile>
    </DocumentationFile>

Please roll back these changes.

Finally, there are now 2 overloads of FSize(). One with a single parameter and one with two parameters with a default value for the 2nd parameter. That does not make sense. Either you add a default value, and then you do not need the 2nd overload, or you do not add the default value.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements missing Visual FoxPro (VFP) filename-parameter support for several file/date functions and adjusts FSIZE() behavior under SET COMPATIBLE ON to match VFP semantics, with accompanying documentation and tests.

Changes:

  • Updated FDate() / FTime() to operate on a provided filename and return correct date/datetime/time values.
  • Added filename-based overloads for FName() and FAttrib() in the VFP runtime.
  • Modified FSize() to return file size in bytes when SET COMPATIBLE is enabled (including the “auto-append .dbf” edge case), and added unit tests/docs updates.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/Runtime/XSharp.VFP/XSharp.VFP.xsproj Debug build property tweaks (warnings/docs settings).
src/Runtime/XSharp.VFP/RuntimeCoreWrappers.prg Implements VFP-compatible FSIZE() behavior based on SET COMPATIBLE.
src/Runtime/XSharp.VFP/LanguageCoreWrappers.prg Implements filename-aware FDATE() / FTIME() behavior.
src/Runtime/XSharp.VFP/FileFunctions.prg Adds filename overloads for FNAME() and FATTRIB().
src/Runtime/XSharp.VFP.Tests/FileVersionTests.prg Adds tests for FDate/FTime and FSize compatibility behavior.
src/Docs/VfpRuntimeDocs.xml Adds documentation entries for the new/extended functions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +57 to +77
File.WriteAllText(tempFile, "123456789012345")

SET COMPATIBLE OFF
VAR nSizeOff := FSize(tempFile)
Assert.Equal(0, nSizeOff)

SET COMPATIBLE ON
VAR nSizeOn := FSize(tempFile)
Assert.Equal(15, nSizeOn)

// VFP states: if no extention is passed then assume DBF
VAR tempDbf := Path.ChangeExtension(tempFile, ".dbf")
File.Copy(tempFile, tempDbf)
VAR cNoExtension := Path.Combine(Path.GetDirectoryName(tempDbf), Path.GetFileNameWithoutExtension(tempDbf))

VAR nSizeDbf := FSize(cNoExtension)
Assert.Equal(15, nSizeDbf)

SET COMPATIBLE OFF
File.Delete(tempFile)
File.Delete(tempDbf)
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FSizeCompatibleTest changes global state with SET COMPATIBLE ON/OFF, but reset to OFF happens only at the end. If an assertion fails, later tests may run with COMPATIBLE still ON. Use TRY ... FINALLY to always restore the previous setting (and delete temp files).

Suggested change
File.WriteAllText(tempFile, "123456789012345")
SET COMPATIBLE OFF
VAR nSizeOff := FSize(tempFile)
Assert.Equal(0, nSizeOff)
SET COMPATIBLE ON
VAR nSizeOn := FSize(tempFile)
Assert.Equal(15, nSizeOn)
// VFP states: if no extention is passed then assume DBF
VAR tempDbf := Path.ChangeExtension(tempFile, ".dbf")
File.Copy(tempFile, tempDbf)
VAR cNoExtension := Path.Combine(Path.GetDirectoryName(tempDbf), Path.GetFileNameWithoutExtension(tempDbf))
VAR nSizeDbf := FSize(cNoExtension)
Assert.Equal(15, nSizeDbf)
SET COMPATIBLE OFF
File.Delete(tempFile)
File.Delete(tempDbf)
VAR tempDbf := ""
TRY
File.WriteAllText(tempFile, "123456789012345")
SET COMPATIBLE OFF
VAR nSizeOff := FSize(tempFile)
Assert.Equal(0, nSizeOff)
SET COMPATIBLE ON
VAR nSizeOn := FSize(tempFile)
Assert.Equal(15, nSizeOn)
// VFP states: if no extention is passed then assume DBF
tempDbf := Path.ChangeExtension(tempFile, ".dbf")
File.Copy(tempFile, tempDbf)
VAR cNoExtension := Path.Combine(Path.GetDirectoryName(tempDbf), Path.GetFileNameWithoutExtension(tempDbf))
VAR nSizeDbf := FSize(cNoExtension)
Assert.Equal(15, nSizeDbf)
FINALLY
// Always restore COMPATIBLE to OFF and clean up temp files
SET COMPATIBLE OFF
IF ! String.IsNullOrEmpty(tempFile) .AND. File.Exists(tempFile)
File.Delete(tempFile)
ENDIF
IF ! String.IsNullOrEmpty(tempDbf) .AND. File.Exists(tempDbf)
File.Delete(tempDbf)
ENDIF
END TRY

Copilot uses AI. Check for mistakes.
RETURN System.IO.Path.GetFileName(XSharp.Core.Functions.FPathName())
ENDIF

RETURN ""
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New FName(cFileName) behavior is introduced here but isn’t covered by unit tests. Add tests for existing vs missing file and verify the returned basename matches expected behavior.

Suggested change
RETURN ""
RETURN System.IO.Path.GetFileName(cFileName)

Copilot uses AI. Check for mistakes.
Comment on lines +295 to +299
[FoxProFunction("FATTRIB", FoxFunctionCategory.FileAndIO, FoxEngine.RuntimeCore, FoxFunctionStatus.Full, FoxCriticality.Medium, "X# Extension")];
FUNCTION FAttrib(cFileName AS STRING) AS DWORD
IF XSharp.Core.Functions.File(cFileName)
VAR cFullPath := XSharp.Core.Functions.FPathName()
VAR info := System.IO.FileInfo{cFullPath}
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New FAttrib(cFileName) behavior is introduced here but isn’t covered by unit tests. Add tests for existing vs missing file and verify key attribute flags behave as expected.

Copilot uses AI. Check for mistakes.
RETURN 0

FUNCTION FSize(cFieldName AS STRING) AS INT
RETURN FSize(cFieldName, NULL)
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new single-parameter overload FSize(cFieldName AS STRING) forwards NULL for eWorkArea. Since the main overload checks IsNil(eWorkArea) to trigger the SET COMPATIBLE file-size behavior (and to use the current work area by default), passing NULL changes semantics and can prevent the compatible-on file-size path from running. Forward NIL (or remove this overload entirely and rely on the default parameter on the main overload).

Suggested change
RETURN FSize(cFieldName, NULL)
RETURN FSize(cFieldName, NIL)

Copilot uses AI. Check for mistakes.
File.Delete(tempFile)
END METHOD

[Fact, Trait("Category", "FDate")];
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FSizeCompatibleTest is tagged with Trait("Category", "FDate"), which makes it harder to filter/triage failures by feature. Consider using a FSize category (or multiple traits if you want both).

Suggested change
[Fact, Trait("Category", "FDate")];
[Fact, Trait("Category", "FSize")];

Copilot uses AI. Check for mistakes.
Comment on lines 288 to +292
/// <include file="VfpRuntimeDocs.xml" path="Runtimefunctions/fdate/*" />
[FoxProFunction("FDATE", FoxFunctionCategory.DateAndTime, FoxEngine.RuntimeCore, FoxFunctionStatus.Full, FoxCriticality.Medium)];
FUNCTION FDate(cFileName AS STRING, nType := 0 AS INT) AS USUAL
RETURN XSharp.Core.Functions.FDate()
IF XSharp.Core.Functions.File(cFileName)
VAR cFullPath := XSharp.Core.Functions.FPathName()
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description references updating FDate()/FTime() in DateFunctions.prg, but this implementation change is in LanguageCoreWrappers.prg. Please align the description (or move the implementation) so the change is discoverable later.

Copilot uses AI. Check for mistakes.
Comment on lines +40 to +77
File.WriteAllText(tempFile, "XSharp VFP File Functions Test")

VAR dFileDate := FDate(tempFile)
Assert.True(IsDate(dFileDate))

VAR dtFileDate := FDate(tempFile, 1)
Assert.True(IsDateTime(dtFileDate))

VAR cFileTime := FTime(tempFile)
Assert.True(cFileTime:Length == 8) // Format HH:mm:ss

File.Delete(tempFile)
END METHOD

[Fact, Trait("Category", "FDate")];
METHOD FSizeCompatibleTest() AS VOID
VAR tempFile := Path.GetTempFileName()
File.WriteAllText(tempFile, "123456789012345")

SET COMPATIBLE OFF
VAR nSizeOff := FSize(tempFile)
Assert.Equal(0, nSizeOff)

SET COMPATIBLE ON
VAR nSizeOn := FSize(tempFile)
Assert.Equal(15, nSizeOn)

// VFP states: if no extention is passed then assume DBF
VAR tempDbf := Path.ChangeExtension(tempFile, ".dbf")
File.Copy(tempFile, tempDbf)
VAR cNoExtension := Path.Combine(Path.GetDirectoryName(tempDbf), Path.GetFileNameWithoutExtension(tempDbf))

VAR nSizeDbf := FSize(cNoExtension)
Assert.Equal(15, nSizeDbf)

SET COMPATIBLE OFF
File.Delete(tempFile)
File.Delete(tempDbf)
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FDateFTimeTest creates a temp file and deletes it only at the end. If an assertion fails, the temp file may be left behind. Use TRY ... FINALLY so the file is always deleted.

Suggested change
File.WriteAllText(tempFile, "XSharp VFP File Functions Test")
VAR dFileDate := FDate(tempFile)
Assert.True(IsDate(dFileDate))
VAR dtFileDate := FDate(tempFile, 1)
Assert.True(IsDateTime(dtFileDate))
VAR cFileTime := FTime(tempFile)
Assert.True(cFileTime:Length == 8) // Format HH:mm:ss
File.Delete(tempFile)
END METHOD
[Fact, Trait("Category", "FDate")];
METHOD FSizeCompatibleTest() AS VOID
VAR tempFile := Path.GetTempFileName()
File.WriteAllText(tempFile, "123456789012345")
SET COMPATIBLE OFF
VAR nSizeOff := FSize(tempFile)
Assert.Equal(0, nSizeOff)
SET COMPATIBLE ON
VAR nSizeOn := FSize(tempFile)
Assert.Equal(15, nSizeOn)
// VFP states: if no extention is passed then assume DBF
VAR tempDbf := Path.ChangeExtension(tempFile, ".dbf")
File.Copy(tempFile, tempDbf)
VAR cNoExtension := Path.Combine(Path.GetDirectoryName(tempDbf), Path.GetFileNameWithoutExtension(tempDbf))
VAR nSizeDbf := FSize(cNoExtension)
Assert.Equal(15, nSizeDbf)
SET COMPATIBLE OFF
File.Delete(tempFile)
File.Delete(tempDbf)
TRY
File.WriteAllText(tempFile, "XSharp VFP File Functions Test")
VAR dFileDate := FDate(tempFile)
Assert.True(IsDate(dFileDate))
VAR dtFileDate := FDate(tempFile, 1)
Assert.True(IsDateTime(dtFileDate))
VAR cFileTime := FTime(tempFile)
Assert.True(cFileTime:Length == 8) // Format HH:mm:ss
FINALLY
IF File.Exists(tempFile)
File.Delete(tempFile)
ENDIF
END TRY
END METHOD
[Fact, Trait("Category", "FDate")];
METHOD FSizeCompatibleTest() AS VOID
VAR tempFile := Path.GetTempFileName()
VAR tempDbf := ""
TRY
File.WriteAllText(tempFile, "123456789012345")
SET COMPATIBLE OFF
VAR nSizeOff := FSize(tempFile)
Assert.Equal(0, nSizeOff)
SET COMPATIBLE ON
VAR nSizeOn := FSize(tempFile)
Assert.Equal(15, nSizeOn)
// VFP states: if no extention is passed then assume DBF
tempDbf := Path.ChangeExtension(tempFile, ".dbf")
File.Copy(tempFile, tempDbf)
VAR cNoExtension := Path.Combine(Path.GetDirectoryName(tempDbf), Path.GetFileNameWithoutExtension(tempDbf))
VAR nSizeDbf := FSize(cNoExtension)
Assert.Equal(15, nSizeDbf)
FINALLY
SET COMPATIBLE OFF
IF File.Exists(tempFile)
File.Delete(tempFile)
ENDIF
IF (!String.IsNullOrEmpty(tempDbf)) .AND. File.Exists(tempDbf)
File.Delete(tempDbf)
ENDIF
END TRY

Copilot uses AI. Check for mistakes.
VAR nSizeOn := FSize(tempFile)
Assert.Equal(15, nSizeOn)

// VFP states: if no extention is passed then assume DBF
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: "extention" should be "extension".

Suggested change
// VFP states: if no extention is passed then assume DBF
// VFP states: if no extension is passed then assume DBF

Copilot uses AI. Check for mistakes.
</filter>
<fname>
<summary>Returns the file name of a specified file path. (X# Extension for VFP Dialect).</summary>
<param name="cFilePath">Specifies the file path from which <b>FName()</b> returns the file name.</param>
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fname docs use <param name="cFilePath">, but the function signature is FName(cFileName AS STRING). This mismatch can confuse generated docs/intellisense; align the parameter name with the actual API (or vice versa).

Suggested change
<param name="cFilePath">Specifies the file path from which <b>FName()</b> returns the file name.</param>
<param name="cFileName">Specifies the file path from which <b>FName()</b> returns the file name.</param>

Copilot uses AI. Check for mistakes.
@Irwin1985
Copy link
Contributor Author

Irwin, For the compatible state you can either request RuntimeState.Compatible or use the function SetCompatible(). We store it as a logical in the runtime state, so there is no need to check for the string value. I see you have also changed some settings in XSharp.VFP:

 <TreatWarningsAsErrors>False</TreatWarningsAsErrors>
    <DocumentationFile>
    </DocumentationFile>

Please roll back these changes.

Finally, there are now 2 overloads of FSize(). One with a single parameter and one with two parameters with a default value for the 2nd parameter. That does not make sense. Either you add a default value, and then you do not need the 2nd overload, or you do not add the default value.

Hi Robert,

Thanks for the feedback. You're right regarding RuntimeState.Compatible. I have rolled back the .xsproj changes and refactored FSize() to use a single overload as you suggested.

However, when I switched to using RuntimeState.Compatible as a logical, I encountered an InvalidCastException. Investigating this, I found that Set.Compatible as being initialized as a string ("OFF") in EnumSet.prg, and the generic Set() function in Set.prg was missing a handler to convert string inputs like "DB4" or "ON" into the expected logical state, which led to the exception.

I have included a fix for these core issues so that the state remains strictly logical. Everything is now working correctly and the unit tests are passing.

Irwin.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants