switch statement - Raesangur/PascalScript_Reference GitHub Wiki
Detailed Syntax
-
switch_statement:
switch
(switchee; (optional) switch_epsilon (or) switch_hash_function): switch_block
-
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
- valid if
-
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 integerdefine 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
- If unused, the preceding
-
switch_block:
{ (1 ... n) case_statement (or) case_type_statement }
-
case_statement:
(optional) case_attribute
case
(case_value): case_block
-
case_value:
- one of
default
error
- expression of type
switchee.type()
.
- one of
-
case_type_statement:
(optional) case_attribute
case
(instanceof
(case_type_value)): case_block
-
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())
- valid if switchee is a pointer or reference of a type that inherits case_type_value
-
case_block:
(0 ... n) statement
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
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
With integer-based and floating-point-based switch cases, allow for ranges of cases to be used with the ...
operator.
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.
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.
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.
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");
}
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