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

Constant evaluation of if/switch with const-evaluation expressions #55

Open theCapypara opened 2 months ago

theCapypara commented 2 months ago

Summary

Add special support for const-evaluation in the headers of if/switch.

Dependencies

This needs the following features to be implemented first:

Motivation

This allows compile time logical evaluation, adding powerful meta-programming functionality to ExplorerScript.

Examples

def 0 {
  if (${3 > 5}) {
    thisWillNotHappen();
  } else {
    thisWillHappen();
  }
}

In this case only the else-branch is evaluated at compile time, only its operations are generated. No Branch opcodes or jumps are actually generated. This means this will be roughly equivalent to this SsbScript:

def 0 {
  thisWillHappen();
}
def 0 {
  switch (${3}) {
    case 1:
      foobar();
      break;
    case 3:
      // OPERATIONS STARTING HERE ARE ACTUALLY GENERATED
      baz();
    case 5:
      foo();
      // END
    case ${4}:
      break;
    case ${41 * 5}:
      break;
  }
}

A const-evaluated switch. Only the code for the second case is generated and for the third via fall-through (no break in the second case). Note that it does not matter if const-evaluation expressions is used in case blocks, this is not affected by this change at all and as is processed as normal in #54. However only simple case blocks are allowed, which means more complicated expressions do require const-evaluation expressions. This means this will be roughly equivalent to this SsbScript:

def 0 {
  baz();
  foo();
}

Language Changes

Parser and Lexer Changes

No expected changes required.

Behaviour

When an if/switch header consists of a single const-evaluation expression these constructs must be evaluated completely different to their usual behaviour.

If / Elseif / Else

The expression in the header of the if's and elseif's are evaluated as would be the runtime-if blocks, however they values are evaluated at runtime. If the value in the header is an integer and >= 0 the condition passes. If it is not an integer this is a compile time error. Once a condition has passed, it's branch block is emitted, the other branch blocks are not emitted.

Switch

The value of the switch header is evaluated at compile-time. This switch only allows simple cases. The exact value of each case is checked against the value in the switch header. The first matching value causes the operations in the case to be taken until a break; or the end of the switch block is hit. No other cases are evaluated or operations are emitted (note however that fall-through is possible if no break; exists before another case is encountered. The cases are ignored in this case, as would be for the runtime-switches.

Loops (while/for)

Note that loops are omitted from this. They continue to function as before. There is no sense in allowing this sort of expression for loops, as it is not possible to assign macro variables at compile time. However compile time loops can be achieved using recursive macro calls.

Compiler Implementation

Compiler Interface Changes

No changes.

Decompiler Changes

No changes.

How to teach

Add this to the section on meta-programming introduced in #54.

Alternatives

Backwards compatibility

This is backwards incompatible as it changes the semantics of how the const-evaluation expression is interpreted inside of a if/switch. This feature should be shipped at the same time as #54. However this is fine, even if this feature is shipped later, as such a syntax would be unusual anyway (since it can just be replaced by omitting the const-evaluation block and directly using the value in most cases. In other cases it can be replaced via indirection with a macro call).