neverwinter.nim icon indicating copy to clipboard operation
neverwinter.nim copied to clipboard

scriptcomp: add initializers to for, while, switch statements

Open tinygiant98 opened this issue 1 year ago • 8 comments
trafficstars

This change adds the ability to use initialization and arbitrary statements within for, while and switch statements. Any variables defined or initialized within a for, while or switch statement will be scoped within the associated compound statement and any child scopes.

All legacy constructs are supported. Initialization statements are optional in for, while or switch statements.

Testing

Simple initializer in for statement:

    for (int i = 0; i < 5; i++)
    {
        Debug(c("    > iteration " + _i(i), COLOR_GREEN_LIGHT));
    }

init

Initializer/declaration list and comma-separated increment statements in for statement:

    for (int x, y = 10, z = -1; y > z; y--, z++, x++)
    {
        Debug(c("    > iteration " + _i(x), COLOR_GREEN_LIGHT));
        Debug(c("      > x = " + _i(x) + ", y = " + _i(y) + ", z = " + _i(z), COLOR_RED_LIGHT));
    }

multiple_init

Function calls in initializer and increments statements in for statement:

    for (object o = GetFirstPC(); GetIsObjectValid(o); o = GetNextPC())
    {
        Debug(c("    > pc found -> " + GetName(o), COLOR_GREEN_LIGHT));
    }

function_calls

Function calls in nested for loops:

    for (object oArea = GetFirstArea(); GetIsObjectValid(oArea); oArea = GetNextArea())
    {
        Debug(c("    > area found -> " + GetName(oArea), COLOR_GREEN_LIGHT));
        for (object o = GetFirstObjectInArea(oArea); GetIsObjectValid(o); o = GetNextObjectInArea(oArea))
        {
            Debug(c("      > object found -> " + GetName(o) + " [" + GetTag(o) + "]", COLOR_ORANGE));
            if (GetObjectType(o) == OBJECT_TYPE_CREATURE)
            {
                for (effect e = GetFirstEffect(o); GetIsEffectValid(e); e = GetNextEffect(o))
                {
                    Debug(c("        > effect found -> " + _i(GetEffectType(e)), COLOR_RED_LIGHT));
                }
            }
        }
    }

nested_loops

Arbitrary statements in initializers and incrementers in for statement:

    int x, y;
    object o = GetFirstPC();
    for (SendMessageToPC(o, "    Loop Init"), y = 10; x < y; SendMessageToPC(o, "        Loop Iteration"), x++, y--)
    {
        SendMessageToPC(o, "      Loop Body");
        SendMessageToPC(o, "        x = " + _i(x) + ", y = " + _i(y));
    }

arbitrary_statements

Simple initializer in while statement:

    while (int x = 7; x > 0)
    {
        Debug(c("    > x = " + _i(x--), COLOR_GREEN_LIGHT));
    }

while_init

Function call in initializer for switch statement

    // SWITCH_INIT = 5
    switch (int x = GetLocalInt(GetFirstPC(), "SWITCH_INIT"); x)
    {
        case 0: Debug(c("    > 0")); break;
        case 1: Debug(c("    > 1")); break;
        case 2: Debug(c("    > 2")); break;
        case 3: Debug(c("    > 3")); break;
        case 4: Debug(c("    > 4")); break;
        case 5: Debug(c("    > 5")); break;
    }

switch_init

Changelog

Added

  • Added initializer statements to for, while and switch statements.
  • Added comma-separated increment statements to for statements.

Licence

  • [x] I am licencing my change under the project's MIT licence, including all changes to GPL-3.0 licenced parts of the codebase.

tinygiant98 avatar Oct 12 '24 16:10 tinygiant98

How does it handle nested loops that use the same variable name? Are they scoped properly?

for (int x = 0; x < 100; x++)
{
    for (int x = 0; x < 10; x++)
    {
        
    }
}

Daztek avatar Oct 12 '24 20:10 Daztek

How does it handle nested loops that use the same variable name? Are they scoped properly?

for (int x = 0; x < 100; x++)
{
    for (int x = 0; x < 10; x++)
    {
        
    }
}

Ran a nested scope test. Results are on discord, but I'll put there here for record-keeping purposes.

Test code snippet:

for (int x = 0; x < 5; x++)
{
    Debug(c("  start outer loop -> x = " + _i(x), COLOR_ORANGE));
    for (int x = 3; x > 0; x--)
    {
        Debug(c("    inner loop -> x = " + _i(x), COLOR_RED_LIGHT));
    }
    Debug(c("  end outer loop -> x = " + _i(x), COLOR_GREEN_LIGHT));
}

image

I think it meets your intent, but if this test is too simple, let me know.

tinygiant98 avatar Oct 12 '24 23:10 tinygiant98

I've run all the tests I can think of here, but I also know how it's supposed to work, so I'm probably missing some ideas on how to break it. If you guys have any tests you'd like run against this PR, let me know and I'll run 'em.

tinygiant98 avatar Dec 01 '24 20:12 tinygiant98

I ran a test on the scrips from my PW, about 1300 files, some are includes of course.

% ./nwn_script_comp --version
...
neverwinter 2.0.2 (master/f407d7, nim 2.2.2)

% ./nwn_script_comp -d ~/sfee/out_master -c ~/sfee/unpacked/nss
I [2025-03-14T18:17:16] 1210 successful, 91 skipped, 0 errored


% ./nwn_script_comp --version
...
neverwinter 2.0.2 (initializers-incrementers/efd9bf, nim 2.2.2)

% ./nwn_script_comp -d ~/sfee/out_tinygiant -c ~/sfee/unpacked/nss 
I [2025-03-14T18:24:09] 1210 successful, 91 skipped, 0 errored

% diff ~/sfee/out_master ~/sfee/out_tinygiant

No output from diff, all ncs files are the same.

I rebased tinygiants PR branch on master for this test, there were no conflicts. Otherwise I got some errors during compile, I think possibly because of a newer nim version.

hendrikgit avatar Mar 14 '25 17:03 hendrikgit

Compiling the scripts from the frozen north also shows no differences.

neverwinter 2.0.2 (initializers-incrementers/efd9bf, nim 2.2.2)
% ./nwn_script_comp -d ~/git/tfn-module/out_tinygiant -c ~/git/tfn-module/src/nss
I [2025-03-14T18:47:11] 2099 successful, 165 skipped, 0 errored

neverwinter 2.0.2 (master/f407d7, nim 2.2.2)
% ./nwn_script_comp -d ~/git/tfn-module/out_master -c ~/git/tfn-module/src/nss
I [2025-03-14T18:50:23] 2099 successful, 165 skipped, 0 errored

% diff out_master out_tinygiant
%

hendrikgit avatar Mar 14 '25 17:03 hendrikgit

If you resolve the merge conflicts and fix the whitespace nits above, I'll click merge.

mtijanic avatar Aug 12 '25 07:08 mtijanic

Ok, will try to clean all that up this week.

tinygiant98 avatar Aug 12 '25 13:08 tinygiant98

@cHackz18 There really aren't any magic numbers in the parser. The parser is broken up into grammar sections and each section has a set of numbered rules that are usually defined in the comments above the grammar section. Within each rule are a set of terms which are essentially just an ordered set of operations based on the possible token combinations under that rule.

If you include me, I believe there are a total of zero people that really understand the compiler in full. If you're interested in understanding more about the parser methodologies, contact me on Discord and we can muddle through it.

tinygiant98 avatar Aug 13 '25 16:08 tinygiant98