65.9K
CodeProject is changing. Read more.
Home

Bird Programming Language: Part 2

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (10 votes)

Sep 22, 2012

GPL3

6 min read

viewsIcon

28713

downloadIcon

344

A new general purpose language that aims to be fast, high level and simple to use. (I renamed it from Anonymus)

Articles About Bird

Table of Contents 

1st Sample Program: Squares

In this sample I draw squares that are fading gradually. When the mouse is over it, it appears.

I created some constants and an array that contains that how much a square is visible:

using System
using BlitzMax

namespace SquaresSample
    const var SquareSize = 32,
              Width = 24,
              Height = 20

    float[Width, Height] Array

The IsMouseOver function returns true if the the mouse is over the specified area:

    bool IsMouseOver(int x, y, w, h)
        return x <= MouseX() < x + w and y <= MouseY() < y + h

The Update function draws the squares and updates the values of the array. It stores the brightness in the Value variable, the position of the square is stored in XPos and YPos:

    void Update()
        for var x, y in 0 .. (Width, Height)
            var Value = Array[x, y]
            var XPos = x * SquareSize
            var YPos = y * SquareSize

If the mouse is over the square it sets the Value to 1, which is the maximal brightness, and draws a white rect:

            if IsMouseOver(XPos, YPos, SquareSize, SquareSize)
                Value = 1
                SetColor 255, 255, 255
                DrawRect XPos, YPos, SquareSize, SquareSize

If the mouse is not over the square and it's not black, it draws the rect with the appropriate color for its position and brightness:

            else if Value > 0
                var Color = 255 * Value
                var Red = (int)(((float)x / Width) * Color)
                var Greed = (int)(((1 - (float)y / Height)) * Color)
                var Blue = (int)(((float)y / Height) * Color)

                SetColor Red, Greed, Blue
                DrawRect XPos, YPos, SquareSize, SquareSize

It decreases the Value variable until 0 and stores it in the array:

            Array[x, y] = Math.Max(Value - 0.02, 0)

The program starts with the Main function. It creates the graphics window with Graphics. In a cycle it constantly clears, redraws and updates the screen (60 times in a second by default). It can be interrupted with pressing Escape or closing the window:

    void Main()
        Memory.Zero Array, sizeof(Array)
        Graphics SquareSize * Width, SquareSize * Height

        while !KeyHit(Keys.Escape) and !AppTerminate()
            Cls
            Update
            Flip

2nd Sample Program: Circles

This sample calculates the color of each pixels, so it's useable for a small comparison with C++. In order to increase the performance, it computes on an image that is smaller than the window and it scales it up.

I made some helper functions. The ArgbColor function combines the four 8 bit components of a color to a single 32 bit number:

using System
using BlitzMax

namespace CirclesSample
    int ArgbColor(byte a, r, g, b)
        return (a to int) << 24 | (r to int) << 16 |
               (g to int) << 8 | b

The GetDistance calculates the distance of two points:

    double GetDistance(double x1, y1, x2, y2)
        var x = x2 - x1, y = y2 - y1
        return Math.Sqrt(x * x + y * y)

The Clamp function creates a byte number that is between 0 and 255 from an integer:

    byte Clamp(int x)
        if x < 0: x = 0
        if x > 255: x = 255
        return (byte)x

The main task is done by  UpdateImage. It calls the LockImage first to make the image modifiable, and it stores how many seconds elapsed since computer startup:

    void UpdateImage(IntPtr Image)
        var Pixmap = LockImage(Image, 0, true, true)
        var Width = ImageWidth(Image)
        var Height = ImageHeight(Image)
        var Time = MilliSecs() / 1000.0

It goes through all pixels with a for loop, calculates its relative position, and its distance to the midpoint of the screen with the time subtracted from it:

        for var x, y in 0 .. (Width, Height)
            var RelX = (x to double) / Width
            var RelY = (y to double) / Height
            var Value = GetDistance(RelX, RelY, 0.5, 0.5) * 3 – Time

From the value it calculates a color:

            var Light = ((RelY * 100) * Math.Abs(Math.Sin(Value / 1.5)) to int)
            var Red = ((RelX * 255) * Math.Abs(Math.Cos(Value)) to int) + Light
            var Green = (((1 - RelY) * 255) * Math.Abs(Math.Sin(Value)) to int) + Light
            var Blue = ((RelY * 255) * Math.Abs(Math.Cos(Value / 3)) to int) + Light

It stores the pixel and at the end of the cycle it calls the UnlockImage function to make the image drawable again:

            var Color = ArgbColor(255, Clamp(Red), Clamp(Green), Clamp(Blue))
            WritePixel Pixmap, x, y, Color

        UnlockImage Image

The Main function only differs from the previous sample that it creates the image that it draws scaled up, and it shows the FPS:

    void Main()
        Graphics 1024, 720

        var Image = CreateImage(512, 360)
        while not KeyHit(Keys.Escape) and not AppTerminate()
            Cls
            UpdateImage Image
            DrawImageRect Image, 0, 0, 1024, 720
            DrawFrameStats
            Flip

Here are the performance results (frames per second) on my Intel Core 2 Dou 1.8 Ghz processor:

Image Size G++ 4.6.2 Clang (LLVM 3.1) Visual C++ 11 Bird
320×240 17 19 27 34

Analyzing the Created Assembly Code

The ArgbColor Function

This function has four parameters that are stored in al, ah, dl, dh registers in this order, the return value is stored in eax. The (a to int) << 24 expression is stored in eax too, because the destination variable is the same register. But it overwrites the value of ah, so it has to be copied to cl. The other sub-expression can be stored in ecx without overwriting anything.

_CirclesSample_ArgbColor:
    mov cl, ah
    and eax, 0xFF
    shl eax, 24
    and ecx, 0xFF
    shl ecx, 16
    or eax, ecx
    movzx ecx, dl
    shl ecx, 8
    or eax, ecx
    movzx ecx, dh
    or eax, ecx
    ret

The GetDistance Function

The first three parameter is stored in xmm0, xmm1, xmm2, the 4th is on the stack. The return value is stored in xmm0. The stack pointer is not saved to ebp in all cases for performance increasement:

_CirclesSample_GetDistance:
    movsd xmm3, xmm0
    movsd xmm0, xmm2
    subsd xmm0, xmm3
    movsd xmm3, qword[esp + 4]
    subsd xmm3, xmm1
    movsd xmm1, xmm0
    mulsd xmm1, xmm0
    movsd xmm2, xmm3
    mulsd xmm2, xmm3
    addsd xmm1, xmm2
    sqrtsd xmm0, xmm1
    ret 8

The Clamp Function

The compiler uses conditional moves in this case to avoid a conditional jump. For the cmov instruction needs the second parameter to be a memory location:

_CirclesSample_Clamp:
    cmp eax, 0
    cmovl eax, dword[_25]
    cmp eax, 255
    cmovg eax, dword[_27]
    ret

3rd Sample Program: Fire

In this sample I created a float array for pixels. These values are increased if the mouse is close to it while blurring the image too. I store the colors for these values in another array. These are the constants and arrays I created:

using System
using BlitzMax

namespace FireSample
    const var
        WindowSize = (1024, 768),
        ImageSize = (560, 420),
        RectSize = 80,
        ColorNumber = 768

    float[ImageSize.0, ImageSize.1] Array
    int[ColorNumber] Colors

I this sample I use tuples, the type of WindowSize and ImageSize is (int, int).
I use two functions from the previous sample:

    int ArgbColor(byte a, r, g, b)
        return (a to int) << 24 | (r to int) << 16 | (g to int) << 8 | b

    byte Clamp(int x)
        if x < 0: x = 0
        if x > 255: x = 255
        return x to byte

I changed the parameter types of the GetDistance function to tuples:

    float GetDistance(float2 P1, P2)
        var x = P1.x - P2.x, y = P1.y - P2.y
        return Math.Sqrt(x * x + y * y) to float

The GetValue function returns with the value of the array at the specified position if they are valid, otherwise it returns zero:

    float GetValue(int x, y)
        if 0 <= x < ImageSize.0 and 0 <= y < ImageSize.1
            return Array[x, y]

        return 0f

The UpdateValues function updates the values of Array. First it blurs the image with the weighted average of the nearby pixels. I dived the sum with 7.1 instead of 7 to make it fading:

    void UpdateValues()
        for var x, y in 0 .. ImageSize
            var Value = Array[x, y] * 5
            Value += GetValue(x + 1, y + 1) * 0.5
            Value += GetValue(x - 1, y + 1) * 0.75
            Value += GetValue(x - 2, y + 2) * 0.5
            Value += GetValue(x - 3, y + 3) * 0.25
            Array[x, y] = Value / 7.1

It calculates the relative position of the mouse and the square that it will be work inside. The Max functions returns the bigger from two numbers, the Min returns the smaller. If the parameters are not scalar numbers, it does the same operation with all their members. The parameter 0 is automatically converted to (0, 0):

        var Mouse = (GetMousePosition() to float2) * ImageSize / WindowSize
        var P1 = Math.Max((Mouse to int2) - RectSize, 0)
        var P2 = Math.Min((Mouse to int2) + RectSize, ImageSize - 1)

Gets the distance for every point and increases the brightness of the pixel based on the distance. The maximal value is one:

        for var x, y in P1 .. P2
            var Dist = 1 - GetDistance(Mouse, (x, y)) / RectSize
            if Dist >= 0f: Array[x, y] = Math.Min(Array[x, y] + Dist / 10f, 1)

The UpdateImage function updates the image with the values stored in Array:

    void UpdateImage(IntPtr Image)
        var Pixmap = LockImage(Image)
        for var x, y in 0 .. ImageSize
            var Color = Array[x, y] * (ColorNumber - 1) to int
            WritePixel Pixmap, x, y, Colors[Color]

        UnlockImage Image

This function calculates the colors for the values:

    void Initialize()
        for var i in 0 .. ColorNumber
            var Value = (i to float) / (ColorNumber - 1)
            var Red = (Math.Min(Value, 1f / 3)) * 3 * 255 to int
            var Green = (Math.Min(Value, 1.65f / 3) - 0.65f / 3) * 3 * 255 to int
            var Blue = (Math.Min(Value, 1f) - 2f / 3) * 3 * 255 to int
            
            Colors[i] = ArgbColor(255, Clamp(Red), Clamp(Green), Clamp(Blue))

The program's main function is similar to the ones in the previous samples. It calls the UpdateValues and UpdateImage function:

    void Main()
        Initialize
        Graphics WindowSize

        var Image = CreateImage(ImageSize)
        while not KeyHit(Keys.Escape) and not AppTerminate()
            Cls
            Update
            UpdateImage Image
            DrawImageRect Image, (0, 0), WindowSize
            DrawFrameStats
            Flip

The performance is alike for all the compilers, but the C++ version is much longer.

4th Sample Program: Reflection

This sample shows how low-level reflection can be used. I plan to make a higher level layer based on it in the future.

I had made two kind of identifiers. The first one is the declared identifier that are usually classes, functions, variables, etc. They have a name. The other group is that are not declared, the compiler generates them, e.g. when getting the address of the variable or making a tuple.

Writing the members to screen:

The first function outputs five members of the Internals.Reflection.Reflection class. To do this it reads the reflection data made by the compiler for it with the ReadDeclaredId functions that returns a DeclaredIdData structure. It contains the name of the identifier, its members, etc. It can be called for the members too to get their name:

using System
using Internals
using Internals.Reflection

namespace ReflectionSamples
    void ListMembers()
        Console.WriteLine "-ListMembers-----------------------------------------"
        var IdData = Reflection.ReadDeclaredId(id_desc_ptr(Reflection))
        var Count = Math.Min(5u, IdData.Members.Length)
        
        for var i in 0 .. Count
            var IdData2 = Reflection.ReadDeclaredId(IdData.Members[i])
            Console.WriteLine IdData2.Name
            IdData2.Free
            
        IdData.Free
        Console.WriteLine

Output:

-ListMembers-------------------------------------------
EntryAssembly
GetAliasBase
GetRealId
IsEquivalentHelper
GetDeclaredEquivalent

Comparing Two Types

The next function outputs the type of an object and compares it with another. The WriteLine calls the object's ToString method if the object is not a string. It returns the name of the type by default:

    void TypeCompare()
        object Obj = (0, 1, 2)
        Console.WriteLine "-TypeCompare-----------------------------------------"
        Console.WriteLine Obj 
        IDENTIFIER_PTR Type = id_desc_ptr((int, int, int))
        Console.WriteLine Reflection.IsEquivalent(ObjectHelper.GetType(Obj),Type)
        Console.WriteLine

Output:

-TypeCompare-------------------------------------------
(int, int, int)
True

Modifying a variable

This function modifies the value of a variable with the help of DeclaredIdData.Address:

    int GlobalVariable
    void SettingGlobal()
        Console.WriteLine "-SettingGlobal-----------------------------------------"
        var Id = id_desc_ptr(GlobalVariable)
        var IdData = Reflection.ReadDeclaredId(Id)
        *(int*)IdData.Address = 123123
        IdData.Free
        
        Console.WriteLine GlobalVariable.ToString()
        Console.WriteLine

Output:

-SettingGlobal-----------------------------------------
123123

Calling a function

The CallingFunction converts the DeclaredIdData.Address member to a function pointer and calls it:

    void CallingFunction()
        Console.WriteLine "-CallingFunction-------------------------------------"
        var Id = id_desc_ptr(Function)
        var IdData = Reflection.ReadDeclaredId(Id)
        var FuncPtr = IdData.Address to (static string -> void)
        IdData.Free
        
        FuncPtr "Function called"
        Console.WriteLine
        
    void Function(string Str)
        Console.WriteLine "Str = " + Str

Output:

-CallingFunction---------------------------------------
Str = Function called

Function Parameters

The type of all variables are undeclared identifiers. They are described by the UndeclaredIdData structure. The Type member specifies what kind of identifier it is. e.g. it could be UndeclaredIdType.Pointer. All declared identifiers can be referenced with UndeclaredIdType.Unknown value, the UndeclaredIdData.DeclaredId member stores its pointer.

The FunctionData first makes sure that the given identifier is a function:

    void FunctionData(IDENTIFIER_PTR Id)
        Console.WriteLine "-FunctionData----------------------------------------"
        var IdData = Reflection.ReadDeclaredId(Id)
        if IdData.Type != DeclaredIdType.Function
            Console.WriteLine "Not a function"
            IdData.Free

If it's a function, then it reads the identifier's type data. The type of a functions contains the return value, parameters, etc. It uses the GetUndeclIdName function to determine the name of their type that are undeclared identifiers too:

        else
            UNDECLARED_ID_PTR FuncType = IdData.BaseUndeclaredId
            IdData.Free

            var TypeData = Reflection.ReadUndeclaredId(FuncType)
            Console.WriteLine "Function: " + Reflection.GetFullName(Id)
            Console.WriteLine "Return type: " + GetUndeclIdName(TypeData.BaseUndeclaredId)
            Console.WriteLine "Parameters:"
            for var i in 0 .. TypeData.Parameters.Length
                var ParamType = GetUndeclIdName(TypeData.Parameters[i].UndeclaredType)
                var ParamName = TypeData.Parameters[i].Name 
                Console.WriteLine ParamType + " " + ParamName

            TypeData.Free

Determining the name of an undeclared identifier is not simple, so it only returns its name, if it refers to a declared one or it has a declared equivalent like UndeclaredIdType.String:

    string GetUndeclIdName(UNDECLARED_ID_PTR UndeclId)
        var UndeclData = Reflection.ReadUndeclaredId(UndeclId)
        var UndeclType = UndeclData.Type
        
        if UndeclType == UndeclaredIdType.Unknown
            var DeclId = UndeclData.DeclaredId
            UndeclData.Free
            return Reflection.GetFullName(DeclId)
        else
            UndeclData.Free

            var DeclId = Reflection.GetDeclaredEquivalent(UndeclType)
            if DeclId != null: return Reflection.GetFullName(DeclId)
            return "???"        

Output:

-FunctionData------------------------------------------
Function: ReflectionSamples.GetUndeclIdName
Return type: System.String
Parameters:
Internals.Reflection.UNDECLARED_ID_PTR UndeclId

The Main function

It calls the previous sample functions:

    void Main()
        ListMembers
        TypeCompare
        SettingGlobal
        CallingFunction
        FunctionData id_desc_ptr(GetUndeclIdName)