corx


A statically typed, general-purpose programming language, crafted for simplicity and performance.

"Trust the programmer" - corx








# Declaration

A function declaration involves, defining return type, then name / tag / identifier of the function and it's parameters. If there's no parameters, it can be left empty or pass void to that function to explicitly specify that, this function doesn't accept any parameters.

A function that doesn't return anything and takes to parameter.


void width() {
    print("Hello, there");
}
        

A function that returns int and takes no parameter.


int width(void) {
    return 50;
}
        

This is identical to above:


int width() {
    return 50;
}
        

# Function Parameters

In this example, we define a simple function square that takes an integer num as a parameter and returns its square. The function is called with the value 5, and the result is printed, showing 25.

In corx parameters are pass-by-value by default.


int square(int num) {
    return num * num;
}

int num = 5;
int sqr = square(num);

print(sqr); # prints 25
        

# Default Arguments

In corx, you can define default arguments for functions, allowing you to call the function without specifying all the parameters. If a parameter is not provided, the default value is used.


float add(float a, int b = 5.3) {
    return a + b;
}

float res = add(2.3);

print(res); # prints 7.6
        

In this example, the function add is called with one argument (2.3), and the default value 5.3 is used for b.

Overriding default argument:


float add(float a, int b = 5.3) {
    return a + b;
}

float res = add(2.3, 1.2);

print(res); # prints 3.4
            

Here, the default value of b is overridden by the provided argument 1.2.


# Pass by Reference

In corx, parameters are passed by value by default, meaning that a copy of the argument is used within the function. To pass a parameter by reference, the (@) symbol is used. This allows the function to modify the original argument.

Default behavior:


void increment(int num) {
    num++; # temporary copy gets incremented
}

int amount = 5;

print(amount); # prints 5

increment(amount);

print(amount); # prints 5
        

In this example, the value of amount remains unchanged because the increment function works on a copy of the argument.

When passed by reference:


void increment(int @num) {
    num++; # original value gets incremented
}

int amount = 5;

print(amount); # prints 5

increment(amount);

print(amount); # prints 6
        

By using the (@) symbol, the increment function modifies the original amount variable, and the value is updated accordingly.


# Return by Reference


int @increment(int @num) {
    num++; # original value gets incremented

    return num; # return reference to 'num'
}

int amount = 5;

print(amount); # prints 5

int @res = increment(amount); # 'res' gets the modified value of 'amount' (6)

print(res); # prints 6

res++; # increment the value of 'res'

print(amount); # prints 7, as 'res' is a reference to 'amount'
        

# Function Type

The following examples demonstrate a mechanism to replace function pointers using named types and type-safe function selection. This approach emphasizes clarity, simplicity, and alignment with corx's philosophy.

Basic use of function types to swap functions based on context.


# define operation type for functions that take two ints and return an int
type int operation(int, int);

# addition function
int addition(int a, int b) {
    return a + b;
}

# multiplication function
int multiply(int a, int b) {
    return a * b;
}

# calculate applies the given operation to two numbers
int calculate(int a, int b, operation op) {
    return op(a, b);
}

# example usage
int add = calculate(5, 6, addition);  # 11
int mul = calculate(5, 6, multiply);  # 30
        

Example of swapping functions using a selector with callback functionality.


# selector for choosing operation based on opcode
type int operation(int, int);

int addition(int a, int b) {
    return a + b;
}

int multiply(int a, int b) {
    return a * b;
}

# select operation based on opcode
operation select(int opcode) {
    switch(opcode) {
        case 1:
            return addition;  # add
        case 2:
            return multiply;  # multiply
        default:
            return null;      # invalid
    }
}

# usage of select function
operation op = select(1);  # select addition
int res = op(2, 3);

print(res);  # 5
        

Demonstrates context-driven function swapping using a calculation function.


# define operation and calculate types
type int operation(int, int);
type int calculate(int, int, operation);

int addition(int a, int b) {
    return a + b;
}

int multiply(int a, int b) {
    return a * b;
}

# calculate applies operation to numbers
int calculate(int a, int b, operation op) {
    return op(a, b);
}

# render calls calculate with different operations
void render(int a, int b, calculate calc) {
    int add = calc(a, b, addition);  # add
    print("addition: ", add);

    int mul = calc(a, b, multiply);  # multiply
    print("multiplication: ", mul);
}

render(3, 4, calculate);  # addition: 7 multiplication: 12
        

# Forward Declaration

A forward declaration allows you to declare a function prototype before its usage. It specifies the function's return type, name, and parameter list without providing the implementation. This is useful when a function is used before being defined.


int add(int a, int b); # with parameter names
# or
int add(int, int);     # without parameter names
        

Forward declarations ensure the compiler knows about the function's signature ahead of time, enabling better modular code organization.


# Inline Functions

An inline function is a hint to the compiler to replace the function call with the actual code of the function to reduce overhead. This can improve performance for small, frequently called functions.


type int fnt(int); # function type

# inlined square function
inline int square(int a) {
    return a * a;
}

# function to use the callback
int usecb(fnt fn, int x) {
    return fn(x);
}

# using callback with the square function
int cb = usecb(square, 3);  # 9

print(cb);  # prints 9
        

Here, the square function is marked as inline. While the behavior of the program remains the same, the compiler might optimize it by replacing the function call with its implementation, potentially reducing execution time.


# Function Overloading

Function overloading allows multiple functions to have the same name but differ in the number or type of their parameters. The correct function is selected based on the argument types during the function call.


int add(int a, int b) {
    return a + b;
}

float add(float a, float b) {
    return a + b;
}

int int_result = add(2, 3);          # calls int version, result is 5
float float_result = add(2.5, 3.5);  # calls float version, result is 6.0
        

# Variadic Function

A variadic function is a function that accepts a variable number of arguments. It allows you to pass any number of arguments of different types.

With single type:


int sum(int args...) {         # variable name, only int type is allowed
    vargs data = vainit(args);

    int res = 0;

    foreach (arg in data) {
        print(arg);
    }

    # automatic cleanup any data existed

    return res;
}
        

Mixed numeric types:


float sum(void args...) {      # variable name, any type is allowed
    vargs data = vainit(args);

    float res = 0;

    foreach (arg in data) {
        res += arg;            # other type has undefined behavior
    }

    # automatic cleanup any data existed

    return res;
}
        

Mixed types:


void print(void args...) {      # variable name, any type is allowed

    # process with extra data using same type 'vargs'
    # for both 'vainit' and 'vaxinit' is possible due to polymorphism
    vargs data = vaxinit(args);

    foreach (arg in data) {
        if (typeof arg = 5) {   # typeof returns type id
            print("string");
        }
    }

    # automatic cleanup any data existed
}
        

# Function and const Keyword

The const keyword can be used with function parameters to ensure that they cannot be modified within the function. This is typically used for parameters passed by reference.


void render(const int @num) {
    print(num);  # num cannot be modified
}

int value = 10;

render(value);   # prints 10
        

# Error Handling