function - Raesangur/PascalScript_Reference GitHub Wiki
Functions are named objects that regroup a series of statements in a callable package.
Detailed Syntax
-
(optional) fnct_attributes
(optional) fnct_template_decl
(optional) fnct_access_specifiersfn
fnct_name ((optional) parameter_list) (optional) -> fnct_return_decl
: statement -
- See Function Attributes.
- Any of:
#discardable
#weak
#default
#delete
#override
#virtual
#inline
#interrupt
- Any other general attribute.
-
- See Templates.
#template
<template_parameter_list>
(optional)#valid_if
(template_constraint (optional), template_constraint_message) -
- See Access Specifiers
- One of:
public
protected
private
-
- Function name declarator. If another function with the same name, overload rules applies.
-
- Zero or more comma-separated list of named function parameter declarators. If the function does not take parameters, this field can be empty.
- Each function parameter can take the following syntax:
(0 ... n) fnct_parameter_attribute type fnct_parameter_name (optional) = fnct_parameter_initializer
-
- Zero or more comma-separated return types declarators. If the function does not return a value, this field can be empty.
- This field, if not empty, is preceded by the
->
operator and followed by a:
.
(0 ... n) type (optional) return_parameter_name, (last
,
omitted) -
- One or more comma-separated list of named [template_parameter](#template parameter) declarators.
-
- A template parameter can be either a type-specific template parameter, or a value-specific template parameter:
type
template_parameter_name (optional) = template_parameter_initializer- OR
type template_parameter_name (optional) = template_parameter_initializer
- A template parameter can be either a type-specific template parameter, or a value-specific template parameter:
-
- A compile-time boolean expression, using the values and/or types from the template parameter lists.
- If the boolean expression resolves to
false
, an error is produced and the template_constraint_message is displayed if provided.
-
- A compile-time string literal to be displayed to the programmer if the template_constraint expression was to resolve to
false
.
- A compile-time string literal to be displayed to the programmer if the template_constraint expression was to resolve to
-
- Any variable attribute.
- A type-specific template_parameter
-
- Variable name declarator.
-
- Variable name declarator.
-
- A default value to be assigned to fnct_parameter_name if none is provided.
- See Default parameter initializers
- Valid if the value is of the same type as the parameter
fnct_parameter_name.type() == fnct_parameter_initializer.type()
-
- Variable name declarator
-
template_parameter_initializer:
- A default value or type to be assigned to template_parameter_name if none is provided.
- If the associated template_parameter_name is a type-specific template, needs to be a type.
std.type_traits.is_type(template_parameter_initializer) == true
- If the associated template_parameter_name is a value-specific template, needs to be of the same type as template_parameter_name.
template_parameter_name.type() == template_parameter_initializer.type()
fn addition(int a, int b) -> int:
return(a + b);
// This variadic function can take an infinite amount of parameters
fn addition(int... manyParameters) -> int sum:
{
std.io.println(manyParameters.count());
std.io.println("Starts with: " + manyParameters[0]);
std.io.println("Ends with: " + manyParameters[-1]);
int sum = 0;
loop(int val in manyParameters):
{
sum += val;
}
return sum;
}
#template<type T, T c = 0>
#valid_if(T.has_member(operator+(T)) == true &&
T.operator+(T).returns()[0] == T, "Addition must be able to be performed on specified type " + T.name())
fn addition(T a, T b) -> T:
return a + b + c;
// a protected function is visible in the same module and all submodules
protected fn division_with_remainder(int numerator, int denominator) -> int res, int:
{
// Division by 0 is defined behavior, returns int.max()
// Modulo by 0 is defined behavior, returns 0
int result = numerator / denominator;
int remainder = numerator % denominator;
return result, remainder;
}
// discardable attribute allows the return value to be discarded without warnings.
// unused attribute allows the unusedValue variable to be unused without warnings.
private fn print_int(int valueToPrint, #unused int unusedValue = 0) -> #discardable int l:
{
io.println(valueToPrint);
return valueToPrint.to_string().length();
}
public fn foo():
{
// Call the `print_int` function will all parameters
print_int(1, 0);
// Call the `print_int` function with only the non-default parameter
print_int(2);
// Call the `print_int` function by specifying parameter access
print_int(valueToPrint: 3);
}
#weak // The #weak attribute allows this function's behavior to be replaced at compile-time by another function with the same signature.
public fn print_message(): // A function taking no arguments and returning nothing has a signature consisting of a single name and empty parentheses.
io.println("Good morning");
// A function with no access specifier will default to `protected`.
#template<type IntType>
#valid_if(std.type_traits.is_integer(IntType))
fn outer_function(IntType valueA = IntType.max(), IntType valueB):
{
// Embedded function
#template<type AnotherIntType>
#valid_if(AnotherIntType == IntType)
public function inner_function(AnotherIntType valueA = 0, IntType valueB = 0) -> AnotherIntType:
// inner_function's valueA and valueB shadow outer_function's variables
{
return valueA + valueB;
}
// inner_function being public means it is accessible via outer_function<T>.inner_function
IntType val1 = inner_function<IntType>(valueB: valueA); // No need to specify inner function's valueA as it has a default value
#unused IntType val2 = inner_function<IntType>(val1, valueB); // Regular C++-like function call
#unused IntType val3 = inner_function<IntType>(); // Using inner function's default parameters
#unused IntType val4 = inner_function<>(); // Automatic template parameter deduction (see Note 1)
/* IntType val5 = inner_function<>(valueB: val1, valueA: val2); */ // ERROR: name-specified parameters must be in order.
}
Note 1 (see Example 7): Automatic template deduction will try int
type first, as value 0
assigned to valueA
and valueB
is an int
literal. If IntType
is int
, this will succeed. If IntType
is not int
, conversion will have to happen, as the #valid_if
attribute requires AnotherIntType
to be the same as IntType
, and 0
is deduced to be int
. It will then attempt to convert the 0
int
into IntType
to put it into AnotherIntType
, the same way long i = 0;
would convert 0
into a long
before putting it into i
. As long as 0
can be converted into the destination type IntType
, automatic template deduction will succeed, which should always be the case as std.type_traits.is_integer(IntType)
needs to be true for the evaluation of inner_function's automatic template deduction.
Attributes can be added to a function, preceded by a #
operator, to add extra information to the function, either for compilation optimization, for warning supression (or to add warnings), or to add some features to the function.
The following attributes are available for functions:
-
- The
#discardable
attribute is applied specifically to the return parameters, either as a whole or for each parameter individually. This attribute suppresses warnings if the return values are not captured during the function call.
fn foo() -> #discardable int: { return 1; } #discardable fn bar() -> int, int: { return 2, 3; } fn baz() -> int, #discardable int: { return 4, 5; }
- The
-
- When the
#weak
attribute is applied to a function, this means that a function of similar signature can completely overwrite theweak
function. Theweak
function then acts as a default implementation of the function, that can be overwritten if desired. - An example of this are all the functions and classes of the Standard PascalScript Library, that are all marked
#weak
to allow for default functionalities to be customized and extended. - The
super
keyword can be used within an overwriting function to call theweak
function
#weak public fn foo(): { io.println("Default foo"); } #weak public fn foo(int a): { io.println("Default foo with parameter"); } public fn foo(): { io.println("Good morning"); } // Error: name conflict between overloaded function foo (with good morning) and foo (with good evening) /* public fn foo(): { io.println("Good evening"); super.foo(); } */ public fn bar(): { foo(); foo(0); }
Output:
Good morning
Default foo with parameters
- When the
-
- In classes, some methods have default implementations, that can be specified using the
default
attribute. Using thedefault
attribute before a function signature means that no fnct_body_block is given, and that the default behavior is instead desired. This is only a tool for verbosity. - In addition, a
default
constructor with named parameters matching member properties names and types will attempt to initialize the member properties.
// These two classes will behave the same public class Foo: { int a = 0; #default constructor(); #default operator==(); } public class Bar: { int a = 0; }
public class Baz: { int a = 0; // Error: a default method cannot have a body /* #default constructor(): { a = 1; }; */ }
public class Qux: { int a; // This default constructor will initialize member property a with the content of parameter a #default constructor(int a); }
- In classes, some methods have default implementations, that can be specified using the
-
- Similar to #default, the
delete
attributes applies to methods with a default implementation. Thedelete
attribute tells the compiler not to have any implementation of the specific method. - This attribute could be used to ensure no copy constructor is created, or to forbid move operations of an object.
public class Foo: { int a = 0; // This class' constructor cannot be invoked with no arguments #delete constructor(); constructor(int val): a = val; };
- Similar to #default, the
-
- The
#inline
attribute is a compiler hint to increase the likelyhood of the compiler completely inlining a function. - The
#inline(force)
attribute forces the compiler to inline the function, unless it is not possible to do so. This is not an inline guarantee. - The
#inline(never)
attribute prevents the compiler from inlining the function. This guarantees the function never gets inlined.
- The
-
- The
#interrupt
attribute is used for interrupt functions that may be called from outside of the program execution flow and interrupt it. - The compiler will generate function entry and exit to preserve the current state of the program at the entry and exit of the specified function.
- The compiler will check global-scoped objects accessed in the function to make sure these are not wrongfully optimized (removing the need of the
volatile
keyword).
- The
Access Specifiers are used for inclusion in modules. The different function access specifiers allow for a specific function to be visible in all files including the module, in all submodules or only within a specific module. If no access specifiers are specified for a function, the protected
access specifier is implied.
The different functions access specifiers are:
- public
: The function is visible to all modules including the specific module.
- protected
: The function is only visible in the same module and its submodules.
- private
: The function is only visible in the same module.
Function overloading allows two function with different input or output parameters to share the same name. The function which signature matches the call will be resolved and called.
Return parameters can be used to distinguish function signatures, unless the return parameters has the #discardable
attribute. A #discardable
function return parameter is considered missing for the intent of overload differentiation.
fn foo(): // FOO 1
return;
fn foo(int): // FOO 2
return;
fn foo() -> int: // FOO 3
return;
fn foo(int) -> int: // FOO 4
return;
fn foo(std::string): // FOO 5
return;
fn foo() -> std::string: // FOO 6
return;
fn main():
{
foo(); // Calls FOO 1
foo(0); // Calls FOO 2
int a = foo(); // Calls FOO 3
std::string b = foo(); // Calls FOO 6
}
Default parameter initializers can be added to any function parameters to give them a default value that will be inserted into them if a value is not provided instead.
fn foo(int a = 2, int b = 0) -> int:
return a + b;
fn bar(int a = 0, int b) -> int:
return a + b;
fn main():
{
io.println(foo()); // Prints 2
io.println(bar(b: 3)); // Prints 3
io.println(foo(1, 2)); // Prints 3
io.println(foo(0)); // Prints 0
}
In order to make code more verbose, or to skip some default parameters, parameter names can instead be used to target which parameter is accessed when passing arguments to a function. The parameter names needs to be inputted in order.
fn addition(int a = 0, int b = 0) -> #discardable int:
{
return a + b;
}
fn main():
{
// Normal function call
addition(1, 1);
// Function call with all parameters name-specified
addition(a: 1, b: 1);
// Function call with parameter `b` name-specified, and parameter `a` not name-specified
addition(1, b: 1);
// Function call where the default values for `a` and `b` are used.
addition();
// Function call where the default value for `a` is used and `b` is given a value
addition(b: 1);
// Error: The parameter names need to match the order of the parameters in the function signature
/* addition(b: 1, a: 1); */
}
TODO: Write text here
When capturing a return parameter by name, the variable into which the return parameter was copied or moved will never emit an unused
warning.
fn return_two_values() -> int a, #discardable int b:
return 1, 0;
fn main():
{
// Capture the two return values
int a1, int b1 = return_two_values();
// Capture the two return values by name
int a2: a, int b2: b = return_two_values();
// This emits a warning as `a` is not discardable
return_two_values();
// Only capture the first value.
// This does not emit a discard warning as `b` is #discardable
// This will however emit an unused warning as `a3` is never used elsewhere in the function and doesn't capture the return value by name
int a3 = return_two_values();
// Only capture the 2nd value.
// This emits a warning as `a` is not discardable
int b3 : b = return_two_values();
int a4, b4;
a4: a, b4 = return_two_values();
if (return_two_values(): a == 1):
io.println("Individual values can be accessed after function call");
};
fn division_with_remainder(int den, int num) -> int result, int remainder:
{
int res = den / num;
int rem = den % num;
return res, rem;
}
fn main():
{
int res1: result = division_with_remainder(10, 3);
int rem1: remainder = division_with_remainder(10, 3);
int res2, int rem2 = division_with_remainder(10, 3);
}