switch statement - Raesangur/PascalScript_Reference GitHub Wiki

Syntax

Detailed Syntax
  • switchee:
    • Expression resulting in a numerical value, an enumerated value, a floating-point, hashable value or a runtime type (as a pointer type or reference type).

  • switch_epsilon:
    • valid if switch_epsilon.type() == switchee.type()
    • If unused, the preceding ; may be skipped.
    • Used only if switchee is a floating-point-resulting expression
      std.type_traits<switchee.type()>.is_floating_point() == true

  • switch_hash_function:
    • If unused, the preceding ; may be skipped.
    • valid if switch_hash_function is a constexpr function taking switchee as a single parameter and returning an integer
      define shf = switch_hash_function,
      std.type_traits<shf.type()>.is_function() &&
      std.type_traits<shf.type()>.is_constexpr() &&
      shf.non_opt_parameters().length() == 1 &&
      shf.parameters()[0].type() == switchee.type() &&
      shf.parameters()[0].is_optional() == false &&
      std.type_traits<shf.returns()[0].type>.is_integer() == true



  • case_value:
    • one of
      • default
      • error
      • expression of type switchee.type().


  • case_type_value:
    • valid if switchee is a pointer or reference of a type that inherits case_type_value
      std.type_traits<switchee.type()>.inherits(case_type_value) &&
      (std.type_traits<switchee.type()>.is_pointer() || std.type_traits<switchee.type()>.is_reference())



Example:

switch(uint var = 0):
{
    case(0):
    {
        std.io.println("Value was 0");
        continue;
    }
    case(1):
        std.io.println("Value below 2");
    
    #likely
    case(2 ... 9):
        std.io.println("Range-case value");
    case(default):
        std.io.println("Default value");
}

Output:

Value was 0
Value below 2


case(error)

Error cases work with switch cases switching on an enumerated value, floating-point values, or with hashes. In the previous example, if case(error) was added, a warning would be produced, because there cannot be errors with the parsing of integer-based switch cases. If no error cases are provided, the default case would be used in case of an error. If neither error of default cases are provided, the switch case would be bypassed in case of an error. Runtime exceptions thrown from different cases do not trigger the error cases. These cases are strictly for parsing errors. The error case is hit in the following conditions:

  • for a floating-point value:
    • any NaN value
    • infinity
    • -infinity
  • for an enum:
    • value not in enum range
  • for a hashable
    • runtime exceptions thrown during hashing

Ranges

With integer-based and floating-point-based switch cases, allow for ranges of cases to be used with the ... operator.


Fallthrough

Cases do not fall through by default. To jump from another case to the next, the continue keyword can be used to jump to the next case in order.
This continue can be located within a subbranch of the case.
This is unlike most modern programming language that have a default continue behavior and require the break keyword to be used to stop a case block from cascading into the next. Given the introduction of ranges of cases to switch statements, this default behavior was no longer desirable.


Likely Unlikely

The #likely and #unlikely attributes allow for smart compiler optimizations, telling the compiler which switch case branch is the most or least likely to occur, so that the compiler can place it accordingly in the execution chain. Optimization behavior is not guaranteed.


Floating-point support

Switch cases can switch on floating-point numbers. An ε must then be provided as 2nd argument to the switch keyword. (Otherwise, switchee.type().epsilon() is used as default value).

float var = 3.141592;
switch(var)     // ε omited, float.epsilon() used.
{
    case(std.math.sqrt(2)):
        std.io.println("irrational √2");
    case(std.math.phi):
        std.io.println("irrational golden number");
    case(std.math.e):
        std.io.println("irrational e");
    case(std.math.pi):
        std.io.println("irrational π");
    case(default):
        std.io.println("Not in supported irrational list");
    case(error):
        std.io.println("Error while parsing number");
};

In this example, the default case would be used, since the default epsilon value is incredibly small. A proper epsilon value could be provided by switching the 2nd line for switch(var; 0.0001). If the switch was executed with such an ε, the proper π case would be selected and executed. If the 1st line was float var = float.NaN();, float var = float.infinity(); or float var = -float.infinity();, the error case would be selected and executed.


Hashing

Beside integers, enums and floating-point values, it is also possible to switch on other hashable values, such a strings or arrays. A hash function must then be provided as 2nd argument to the switch keyword. (Otherwise std.algo.hash<switchee.type> is used if available). If no hash function is available, this results in a compile-time error.

std.string var = "Good morning";
switch(var /*; std.algo.hash */)    // std.algo.hash used by default
{
    case("Hello World"):                // Conversion from const char* to std.string
        std.io.print("Message was Hello World");
    case(std.wstring("Good morning")):  // Conversion from std.wstring to std.string then hashing
        std.io.print("Message was good morning");
    case(1):                            // Conversion from integer 1 to string "1"
        std.io.print("Message was 1");    
    case(null):                         // Uses std.algo.hash(null)
        std.io.print("No message");

    /* case(std::file{"dest.txt"}): */  // Compile-time error, impossible to convert a std.io.file into a std.string at compile-time.
    /* case("Good morning"): */         // Compile-time error, identical hash in two cases

    // If no error case is provided, a runtime exception in the hashing function would be thrown.
    // The error case catches the exception thrown by the hashing function
    case(default):
        std::print("No message or error parsing message");
}

Runtime type checking

Besides values, switch statements can also be used to switch between types at runtime, specifically polymorphic instances through pointer or reference types. In the following example, class Vehicle is defined and classes Truck, Bicycle and Boat are created to inherit from class Vehicle. Type checking cases and value checking cases cannot be mixed in the same switch statement.

Vehicle* myTruck = new Truck();
switch(myTruck)
{
    case(instanceof(Bicycle)):
        continue;
    case(instanceof(Truck)):
        std.io.println("Land vehicle");
    case(instanceof(Boat)):
        std.io.println("Sea vehicle");
}

Output:

Land vehicle

⚠️ **GitHub.com Fallback** ⚠️