function - Raesangur/PascalScript_Reference GitHub Wiki

Functions are named objects that regroup a series of statements in a callable package.

Syntax

Detailed Syntax

Examples

Example 1

fn addition(int a, int b) -> int:
    return(a + b);

Example 2

// 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;
}

Example 3

#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;

Example 4

// 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;
}

Example 5

// 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);
}

Example 6

#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");

Example 7

// 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

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:

  • #discardable:

    • 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.

    Example 8:

        fn foo() -> #discardable int:
        {
            return 1;
        }
    
        #discardable
        fn bar() -> int, int:
        {
            return 2, 3;
        }
    
        fn baz() -> int, #discardable int:
        {
            return 4, 5;
        }
  • #weak:

    • When the #weak attribute is applied to a function, this means that a function of similar signature can completely overwrite the weak function. The weak 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 the weak function

    Example 9:

        #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

  • #default:

    • In classes, some methods have default implementations, that can be specified using the default attribute. Using the default 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.

    Example 10:

    // 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);
    }
  • #delete:

    • Similar to #default, the delete attributes applies to methods with a default implementation. The delete 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.

    Example 11:

    public class Foo:
    {
        int a = 0;
    
        // This class' constructor cannot be invoked with no arguments
        #delete constructor();
    
        constructor(int val):
            a = val;
    };
  • #override:

  • #virtual:

  • #inline(level):

    • 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.
  • #interrupt:

    • 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).

Access Specifiers

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

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.

Example 12:

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

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.

Example 13:

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
}

Name-specified parameters

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.

Example 14:

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); */
}

Name-specified return parameters

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.

Example 15

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");
};

Example 16:

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);
}

TODO: Variadic functions

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