SkyTemple / ExplorerScript

ExplorerScript and SSBScript: Script languages for decompiled SSB (Pokémon Mystery Dungeon Explorers of Sky)
MIT License
16 stars 6 forks source link

User defined constants #51

Closed theCapypara closed 1 month ago

theCapypara commented 2 months ago

Summary

Add user-defined constants defined at compile time by the ExplorerScript compiler itself. These are scoped globally or to their routine/macro.

This proposal talks about "user-defined constants" and "system-defined constants", replacing the term "constants". See "How to teach" for defintions.

Dependencies

Motivation

This would allow users to better structure and organize their code by aliasing common values with custom constants, not merely relying on system provided constants.

Examples

const FOO = 12;
def 0 {
   const BAR = "Hello";
   const BAZ = FOO;
   const BUZZ = ACTOR_ONE;
   myOperation(BAR, 12, BAZ, BUZZ, OBJECT_TWO);
}

Example showing the definition of one global user-defined constant FOO with value 12, and three user-defined constants in def 0, where BAR has the value of a const string "Hello", BAZ has the value 12 (via global FOO) and BUZZ has the value of a system-defined constant ACTOR_ONE. The compiler API compiles myOperation to be equivalent with: myOperation("Hello", 12, 12, ACTOR_ONE, OBJECT_TWO);

Language Changes

Parser and Lexer Changes

New syntax for the const statement is needed. This syntax may be allowed wherever a statement is allowed in a routine as well as outside of routines. Any IDENTIFIER is allowed for the name of a user-defined constant. Any primitive can be assigned to a constant. User-defined constants are matched with primitive's IDENTIFIERs, so no parser changes are needed here.

Behaviour

User-defined constants are evaluated when they are collected. They are collected in their scope, before their scope is processed. They are collected top-down inside their scope:

If a user-defined constant is defined multiple times with the same name in the same scope, it's value is overwritten.

Whenever the compiler encounters an IDENTIFIER in a primitive, it will first look up user-defined constants in the current scope. If found, it is immediately replaced with its evaluated value with the value of the constant. If no user-defined constant is found, the compiler assumes a system-defined constant is meant.

When a user-defined constant is assigned an IDENTIFIER, the same lookup rules as described above are performed. Only user- defined constants from outer scopes or previously defined inside the same scope can be used to be assigned to a user-defined constant, otherwise it will be interpreted as a system defined constant. See this example (evaluation order and result is given in the comments):

const EXAMPLE = 1;  // Evaluated 1st: Evaluates to integer 1

def 0 {
    const FOO = FUZZ; // Evaluated 3rd: Evaluates to system-defined constant FUZZ
    const BAR = BAZ; // Evaluated 4th: Evaluates to system-defined constant FOOBAR (via globally scoped user-defined const. BAZ)
    const FUZZ = BAR; // Evaluated 5th: Evaluates to system-defined constant FOOBAR (via locally scoped BAR -> globally scoped BAZ)
}

const BAZ = FOOBAR; // Evaluated 2nd: Evaluates to system-defined constant FOOBAR

Compiler Implementation

Compiler Interface Changes

No changes.

Decompiler Changes

No changes.

How to teach

First two new terms need to be established to replace "constants":

The documentation must be updated to explain the difference between user-defined and system-defined constants and additionally contain a section on defining user-defined constants. It must not go into too many details about compile-time behavior differences in the main text but should do so in an admonition.

After this change the documentation must not mention "constants" anymore, it must always mention "user-defined" or "system-defined" constants as described above. An exception are places where both are treated equality, there they may be grouped under the term "constants".

Alternatives

Quite a few variations could be thought of, in both syntax and evaluation. This seems to be the most sensible.

Backwards compatibility

This is fully backwards compatible.