From c25d6488f4b40b51c8b727422c0240fa8835526d Mon Sep 17 00:00:00 2001 From: Irwin Rodriguez Date: Fri, 13 Nov 2026 16:28:13 +0100 Subject: [PATCH] DELETE and RECALL with IN take the clauses in any order DELETE ALL FOR x IN t, DELETE NEXT 2 FOR x IN t, RECALL REST WHILE x IN t and similar didn't compile: the rules with IN only matched when the clauses came in the order of the rule. New rules move the IN clause to the front and the existing ones take the rest. Ten tests added. --- src/Common/FoxProCmd.xh | 27 +++ .../XSharp.VFP.Tests/ScopeInAliasTests.prg | 207 ++++++++++++++++++ 2 files changed, 234 insertions(+) create mode 100644 src/Runtime/XSharp.VFP.Tests/ScopeInAliasTests.prg diff --git a/src/Common/FoxProCmd.xh b/src/Common/FoxProCmd.xh index f11855394f..023b7875c3 100644 --- a/src/Common/FoxProCmd.xh +++ b/src/Common/FoxProCmd.xh @@ -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 IN is left alone. The alias goes out +// as it came in, and not <(a)>, or IN (cAlias) would end up as the name "cAlias". +#command DELETE ; + <*clauses*> IN <(a)> ; + => DELETE IN + +#command DELETE ; + <*clauses*> IN <(a)> NOOPTIMIZE ; + => DELETE IN NOOPTIMIZE + +#command RECALL ; + <*clauses*> IN <(a)> ; + => RECALL IN + +#command RECALL ; + <*clauses*> IN <(a)> NOOPTIMIZE ; + => RECALL IN NOOPTIMIZE + #command DELETE ; [FOR ] ; [WHILE ] ; diff --git a/src/Runtime/XSharp.VFP.Tests/ScopeInAliasTests.prg b/src/Runtime/XSharp.VFP.Tests/ScopeInAliasTests.prg new file mode 100644 index 0000000000..df8c82933b --- /dev/null +++ b/src/Runtime/XSharp.VFP.Tests/ScopeInAliasTests.prg @@ -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