Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/Common/FoxProCmd.xh
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,33 @@
)); _TALLY := RuntimeState.Tally


// DELETE and RECALL with an IN clause take the other clauses in any order in VFP:
// DELETE ALL FOR x IN t, DELETE NEXT 2 FOR x IN t, RECALL REST WHILE x IN t.
// The preprocessor accepts the optional clauses of a rule in any order only when
// nothing mandatory follows them, and in the scoped rules below the IN clause does.
// These rules are tried when the scoped ones do not match. They move the IN clause
// to the front, and from there the scoped rules take the rest as it comes.
// They have no optional markers on purpose: with one in the rule the wild marker
// would stop at the first FOR or ALL instead of at IN. That is why NOOPTIMIZE after
// the IN clause has its own rule. The first clause is restricted to the keywords of
// the command so that DELETE TAG <tag> IN <file> is left alone. The alias goes out
// as it came in, <a> and not <(a)>, or IN (cAlias) would end up as the name "cAlias".
#command DELETE <first:ALL,FOR,WHILE,NEXT,RECORD,REST,NOOPTIMIZE> ;
<*clauses*> IN <(a)> ;
=> DELETE IN <a> <first> <clauses>

#command DELETE <first:ALL,FOR,WHILE,NEXT,RECORD,REST,NOOPTIMIZE> ;
<*clauses*> IN <(a)> NOOPTIMIZE ;
=> DELETE IN <a> <first> <clauses> NOOPTIMIZE

#command RECALL <first:ALL,FOR,WHILE,NEXT,RECORD,REST,NOOPTIMIZE> ;
<*clauses*> IN <(a)> ;
=> RECALL IN <a> <first> <clauses>

#command RECALL <first:ALL,FOR,WHILE,NEXT,RECORD,REST,NOOPTIMIZE> ;
<*clauses*> IN <(a)> NOOPTIMIZE ;
=> RECALL IN <a> <first> <clauses> NOOPTIMIZE

#command DELETE ;
[FOR <lfor>] ;
[WHILE <lwhile>] ;
Expand Down
207 changes: 207 additions & 0 deletions src/Runtime/XSharp.VFP.Tests/ScopeInAliasTests.prg
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
//
// Copyright (c) XSharp B.V. All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
//
// DELETE and RECALL with an IN clause and the other clauses in an order that
// is not the one of the rules in FoxProCmd.xh: scope before FOR, WHILE before
// FOR, NOOPTIMIZE after IN. They did not compile. The expected values were
// measured on VFP 9 SP2 with five records, the record pointer of the table on
// record 2 and another work area selected.

USING System
USING XUnit

BEGIN NAMESPACE XSharp.VFP.Tests
CLASS ScopeInAliasTests

STATIC CONSTRUCTOR
XSharp.RuntimeState.Dialect := XSharpDialect.FoxPro
RegisterFoxMemVarSupport()
END CONSTRUCTOR

[Fact, Trait("Category", "Scope")];
METHOD DeleteAllForInAlias AS VOID
LOCAL lOld AS LOGIC
TRY
lOld := SELF:Setup(FALSE)
_TALLY := -1
DELETE ALL FOR curscope->x > 1 IN curscope
Assert.Equal(4, (INT) _TALLY)
Assert.Equal("CUROTHER", Upper(Alias()))
Assert.Equal(".DDDD", SELF:Flags())
FINALLY
SELF:Cleanup(lOld)
END TRY
END METHOD

[Fact, Trait("Category", "Scope")];
METHOD DeleteNextForInAlias AS VOID
LOCAL lOld AS LOGIC
TRY
lOld := SELF:Setup(FALSE)
_TALLY := -1
DELETE NEXT 2 FOR curscope->x > 2 IN curscope
Assert.Equal(1, (INT) _TALLY)
Assert.Equal("..D..", SELF:Flags())
FINALLY
SELF:Cleanup(lOld)
END TRY
END METHOD

[Fact, Trait("Category", "Scope")];
METHOD DeleteRestForInAlias AS VOID
LOCAL lOld AS LOGIC
TRY
lOld := SELF:Setup(FALSE)
_TALLY := -1
DELETE REST FOR curscope->x > 3 IN curscope
Assert.Equal(2, (INT) _TALLY)
Assert.Equal("...DD", SELF:Flags())
FINALLY
SELF:Cleanup(lOld)
END TRY
END METHOD

[Fact, Trait("Category", "Scope")];
METHOD DeleteRestWhileInAlias AS VOID
LOCAL lOld AS LOGIC
TRY
lOld := SELF:Setup(FALSE)
_TALLY := -1
DELETE REST WHILE curscope->x < 4 IN curscope
Assert.Equal(2, (INT) _TALLY)
Assert.Equal(".DD..", SELF:Flags())
FINALLY
SELF:Cleanup(lOld)
END TRY
END METHOD

[Fact, Trait("Category", "Scope")];
METHOD DeleteWhileBeforeForInAlias AS VOID
LOCAL lOld AS LOGIC
TRY
lOld := SELF:Setup(FALSE)
_TALLY := -1
DELETE WHILE curscope->x < 4 FOR curscope->x > 2 IN curscope
Assert.Equal(1, (INT) _TALLY)
Assert.Equal("..D..", SELF:Flags())
FINALLY
SELF:Cleanup(lOld)
END TRY
END METHOD

[Fact, Trait("Category", "Scope")];
METHOD DeleteAllForInAliasFromAVariable AS VOID
LOCAL lOld AS LOGIC
LOCAL cAlias := "curscope" AS STRING
TRY
lOld := SELF:Setup(FALSE)
_TALLY := -1
DELETE ALL FOR curscope->x > 1 IN (cAlias)
Assert.Equal(4, (INT) _TALLY)
Assert.Equal(".DDDD", SELF:Flags())
FINALLY
SELF:Cleanup(lOld)
END TRY
END METHOD

[Fact, Trait("Category", "Scope")];
METHOD DeleteAllForInAliasNoOptimize AS VOID
LOCAL lOld AS LOGIC
TRY
lOld := SELF:Setup(FALSE)
_TALLY := -1
DELETE ALL FOR curscope->x > 1 IN curscope NOOPTIMIZE
Assert.Equal(4, (INT) _TALLY)
Assert.Equal(".DDDD", SELF:Flags())
FINALLY
SELF:Cleanup(lOld)
END TRY
END METHOD

[Fact, Trait("Category", "Scope")];
METHOD RecallAllForInAlias AS VOID
LOCAL lOld AS LOGIC
TRY
lOld := SELF:Setup(TRUE)
_TALLY := -1
RECALL ALL FOR curscope->x > 1 IN curscope
Assert.Equal(4, (INT) _TALLY)
Assert.Equal("CUROTHER", Upper(Alias()))
Assert.Equal("D....", SELF:Flags())
FINALLY
SELF:Cleanup(lOld)
END TRY
END METHOD

[Fact, Trait("Category", "Scope")];
METHOD RecallNextForInAlias AS VOID
LOCAL lOld AS LOGIC
TRY
lOld := SELF:Setup(TRUE)
_TALLY := -1
RECALL NEXT 2 FOR curscope->x > 2 IN curscope
Assert.Equal(1, (INT) _TALLY)
Assert.Equal("DD.DD", SELF:Flags())
FINALLY
SELF:Cleanup(lOld)
END TRY
END METHOD

[Fact, Trait("Category", "Scope")];
METHOD RecallRestWhileInAlias AS VOID
LOCAL lOld AS LOGIC
TRY
lOld := SELF:Setup(TRUE)
_TALLY := -1
RECALL REST WHILE curscope->x < 4 IN curscope
Assert.Equal(2, (INT) _TALLY)
Assert.Equal("D..DD", SELF:Flags())
FINALLY
SELF:Cleanup(lOld)
END TRY
END METHOD

// five records in curscope with the pointer on record 2, and curother selected
PRIVATE METHOD Setup(lAllDeleted AS LOGIC) AS LOGIC
LOCAL lOld AS LOGIC
lOld := SetDeleted(FALSE)
CREATE CURSOR curscope (x I)
INSERT INTO curscope VALUES (1)
INSERT INTO curscope VALUES (2)
INSERT INTO curscope VALUES (3)
INSERT INTO curscope VALUES (4)
INSERT INTO curscope VALUES (5)
IF lAllDeleted
DELETE ALL
ENDIF
GO 2
SELECT 0
CREATE CURSOR curother (g I)
INSERT INTO curother VALUES (1)
GO TOP
RETURN lOld
END METHOD

PRIVATE METHOD Cleanup(lOld AS LOGIC) AS VOID
SetDeleted(lOld)
USE IN curother
USE IN curscope
END METHOD

// one character per record of curscope: D deleted, . not deleted
PRIVATE METHOD Flags() AS STRING
LOCAL cResult := "" AS STRING
SELECT curscope
GO TOP
DO WHILE ! Eof()
cResult += IIF(Deleted(), "D", ".")
SKIP
ENDDO
SELECT curother
RETURN cResult
END METHOD

END CLASS
END NAMESPACE