Advanced Topics - BBpezsgo/Interpreter GitHub Wiki

Extension functions

You can define an extension function as follows:

i32 Add(this i32 a, i32 b) {
  return a + b;
}

If the first parameter defined with the this modifier, the function can be called like this:

i32 sum = 57.Add(2);

You can also call an extension function in a normal way:

i32 sum = Add(57, 2);

Pointers

Yeah there are also pointers.

[!NOTE] Pointers technically aint supported in brainfuck, but you can still pass a variable as a reference to a function anyway.

You can declare a pointer type like this:

i32* yeah;

This will declare a variable "yeah" that points to a 32 bit signed integer.

You can also declare a pointer that points to a struct like this:

struct Vector2
{
    f32 X;
    f32 Y;
}

Vector2* point;

... and you can access its fields like normally you do:

f32 x = point.X;

[!NOTE] If you try to access a field of a zero pointer, depending on the --no-nullcheck argument a runtime exception will be thrown.

You can set the value where the pointer points to like this:

*yeah = 44;

[!NOTE] I want to make a better syntax for field dereferencing, because what if the field is a pointer type and you want to set the pointer's value or the value where the pointer points to? Idk man, I will implement it when I feel like to.

new statement

You can use the new keyword to initialize a value. This is used for structs and arrays.

// Creates a new struct Point and initializes it to zeroes
Point point = new Point;

You can also use the new keyword to allocate memory on the heap:

// This will allocate a new struct Point on the heap
Point* point = new Point*;
// This will allocate an integer on the heap
i32* value = new i32*;
// This will allocate 5 bytes on the heap
u8[]* value = new u8[5]*;

delete statement

You can use the delete keyword to deallocate a value from the heap.

// Allocate an integer on the heap
i32* value = new i32*;

// Deallocate the integer from the heap
delete value;

If there is a constructor defined for the type, that will be called before deallocating the object.

temp modifier

You can use the temp modifier if you don't want to mess with the delete keyword. The temp modifier can be applied to variables and parameters.

temp modifier on variables

If you define a variable with the temp modifier, it will be automatically deallocated when goes out of scope.


void main()
{
  // Allocate an integer on the heap
  temp i32* value = new i32*;
  // Random calculations
  *value = 64;
  *value -= 3;

  // The value will be deallocated here
}

temp modifier on parameters

If you define a parameter with the temp keyword, sometimes it can be used to automatically deallocate the value.

void Print(temp u16[]* text)
{
  // Stuff ...
}

Pr

Structs

You can define a struct as follows:

struct Point
{
  i32 x;
  i32 y;
}

And use it like this:

Point point;
point.x = 37; // Set the "x" field to 37
point.y = 81;

Constructors

You can define a constructor for a struct like this:

struct Point
{
  i32 x;
  i32 y;

  Point(i32 x, i32 y)
  {
    this.x = x;
    this.y = y;
  }
}

And use it like this:

Point point = new Point(37, 81);

Note that if you don't put the parentheses here, you just initialize a value without calling a constructor.

You can think of a constructor like this:

Point point = new Point; // Initializing with zeros
ctor(point, 37, 81); // Calling the corresponding constructor

You can also define multiple constructors for different kind of types:

struct Point
{
  i32 x;
  i32 y;

  Point(i32 x, i32 y)
  {
    this.x = x;
    this.y = y;
  }

  Point*(i32 x, i32 y)
  {
    this.x = x;
    this.y = y;
  }
}

And use them like this:

Point point1 = new Point(37, 81); // This is on the stack
Point* point2 = new Point*(37, 81); // This is on the heap

If there is one constructor defined, that will be used whatever type you want to allocate.

struct Point
{
  i32 x;
  i32 y;

  Point(i32 x, i32 y)
  {
    this.x = x;
    this.y = y;
  }
}

// These two will call the same constructor
Point point1 = new Point(37, 81);
Point* point2 = new Point*(37, 81);

External Functions

External functions are functions that only have the function signature defined and the body written somewhere in C#. They often used to implement APIs.

To import an external function, use the External attribute with the external function's name as a parameter:

[External("stdout")]
void Print(u16 data);

If the return type or parameter types don't match, a compiler error will be made.

Predefined External Functions

[!NOTE] These external functions are only available in default mode.

  • "stdin"

    Reads a key from the console. This blocks the code execution until a key is pressed.

    • Parameters: none
    • Return type: u16

    Corresponding .NET function:

    return (char)System.Console.In.Read();
    
  • "stdout"

    Writes a character to the standard output stream.

    • Parameters: u16 character
    • Return value: void

    Corresponding .NET function:

    System.Console.Out.Write(character);
    
  • "utc-time"

    Returns the elapsed milliseconds since midnight in UTC time.

    • Parameters: none
    • Return value: i32

    Corresponding .NET function:

    return (int)DateTime.UtcNow.TimeOfDay.TotalMilliseconds;
    
  • "local-time"

    Returns the elapsed milliseconds since midnight in local time

    • Parameters: none
    • Return value: i32

    Corresponding .NET function:

    return (int)DateTime.Now.TimeOfDay.TotalMilliseconds;
    
  • "utc-date-day"

    Returns the current day of year in UTC time

    • Parameters: none
    • Return value: i32

    Corresponding .NET function:

    return (int)DateTime.UtcNow.DayOfYear;
    
  • "local-date-day"

    Returns the current day of year in local time

    • Parameters: none
    • Return value: i32

    Corresponding .NET function:

    return (int)DateTime.Now.DayOfYear;
    
  • "utc-date-year"

    Returns the current year in UTC time

    • Parameters: none
    • Return value: i32

    Corresponding .NET function:

    return (int)DateTime.UtcNow.Year;
    
  • "local-date-year"

    Returns the current year in local time

    • Parameters: none
    • Return value: i32

    Corresponding .NET function:

    return (int)DateTime.Now.Year;
    

Index getters & setters

You can define a custom indexer for a struct like this:

struct List
{
  i32[]* _ptr;

  i32 indexer_get(i32 index)
  {
    return this._ptr[index];
  }

  void indexer_set(i32 index, i32 value)
  {
    this._ptr[index] = value;
  }
}

And use it like this:

// This is on the stack
List list1 = new List;
list1[4] = 7;
int v = list1[4];

// This is on the heap
List* list2 = new List*;
list2[4] = 7;
int v = list2[4];

Destructors

You can also define a deconstructor for a struct like this:

struct List
{
  i32[]* _ptr;

  destructor()
  {
    delete this._ptr;
  }
}

The destructor called before deallocating the object.