13,404,127 members (45,506 online)
Article
alternative version

Stats

19.9K views
16 bookmarked
Posted 22 Sep 2012

Bird Programming Language: Part 2

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

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
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)
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)
```

Share

 Software Developer Hungary
No Biography provided