ALA7 language tutorial
Introduction
Program code is devided to units. A unit is a function, a procedures on main module block of instructions.
i=1
x=2
The above code form main module direct instructions.
proc p1() #here starts procedure program unit
x=2
proc p2()
x=4
i=1 #here starts main code
x=2
All not global variables belong to their program units.
The fallowing examples are included in files tut1-5.ala.
To see results of our programming we will use printNL procedure that prints new line on the console screen.
Integer operations
i=1 i=i+2 x=y-z x=a*(a+b) x=x1/x2; y=reminder #reminder from last / (divide) operation x=y >> 2 #shift 2 bits right x=x & y #bit and 1FFF FFFF & FFFF FFF1 -> 1FFF FFF1 x=x | y #bit or 0000 0001 | 1000 0001 -> 1000 0001
String operations
text_1="Alice " #str variable text_1 is declared and initialized to ”Alice” text_3=text_1 #copy all chars not reference text_1="012345678" ;index=2 el_2=text_1[index] #indexes from 0 sub=text_1[1:4] # take from 1 th,2,3 but not 4
Lists
list_1=[] #untyped list i=6; append(list_1,i)
To untyped list we can append any value: int, str or object
list_2=[1,2,3] # equals to list_2=[]; append(list_2,1);... i=0 for item in list_2 #iteration through all elements of list_2 i=item
Variable item has special type all to hold values of all types, because our list is untyped. The variable name must be unique in program unit, for each for .. in instruction.
for e in list_1 #e type is all printNL(e)
Above instructions are an example of dynamic (late) procedure binding. The compiler first tries to find procedure printNL(all x). If procedure is not found the compiler tries to find procedures where name is printNL and have one parameter. If at least one is found than dynamic binding is coded: during run time program tries to find function address for a current actual parameter type.
for e_1 in list_1 #e_1 not e because in: for var in list, var must be unique i=e_1 printNL(i) #static binding
Typed lists
list_int=[int] #typed list of ints list_class_1=[Class_1] #typed list of Class_1 for e_i in list_int printNL(e_i) #static binding to printNL(int i) because compiler knows list_int element type for e_cl_1 in list_3 printNL(e_cl_1) #late binding class_1=e_cl_1; printNL(class_1) #early binding
List variable contains reference to list object. When we assign one list var to another we simply copy reference, so the new variable is pointing to the same list. Objects reference counting is done automaticly. If we want a variable pointing to new list, new operator should be used.
list_1_a=list_1 #list_1_a referes to the same list as list_1 list_1_a=new list #list_1_a referes now to new empty list
When list copy is required use copy function : new list object is created and all elements are copied.
list_1_copy=copy(list_1)
List element and subsets are similar to strings. Indexes also start from 0.
index=0 i_0=list_1[index] #take list element at index=0 list_1_sub=list_1[1:3] #take 1-th,2, but not 3
Pop operation takes off last list element.
i=pop(list_1) index=1 delete(list_1,index) #delete element at index=1
To change list element replace function is to be used. There are two posibilities: first when second argument is a list index and second when for in variable is used.
index=0 new_value=44 replace(list_1,index,new_value) for e_rep in list_1 i=e_rep if i >10 replace(list_1,e_rep,new_value)
Classes
Example class definition:
class Class_1 attr_int=0 attr_str="Ala" attr_list=[] #untyped list attr_list_1=[int] #typed list
Its easy to notice that there are no methods, instead we have function overloading:
proc printNL(Class_1 class_1)
text="Class_1 :attr_int=" + toStr(class_1.attr_int)
text=text + ", attr_str=" + class_1.attr_str
printNL(text)
proc printNL(Class_2 class_2)
To use objects first declaration is required:
Class_1 class_1
To assign values to attributes and to use them full stop notation is used:
class_1.attr_int=66 class_1.attr_str="xxxx 666" i=class_1.attr_int
When we assign one object variable to another object reference is copied, not object and afterwards two variables are pointing to the same object. Object reference counting will be described later. When we want a variable pointing to new or copied object the new clause and copy function are to be used respectively.
class_1=new Class_1 #we want class_1 to refere to new Class_1 object class_1_1=copy(class_1)
Program control
To change program flow if ..eilif else, while, for and goto instructions are used.
x=1
if x=1 :printNL("equal")
if x=1
printNL("equal")
If subsequent instructions are to be in the same line colon ( : ) must be used.
if x=1
printNL("1")
elif x=2
printNL("2")
else
print("others")
While isnstruction
x=0 while x < 4 printNL(x);x=x+1
Result :0 1 2 3 .
For loops
for i=0 to 4 printNL(i)
Result :0 1 2 3 4
for i=4 to 0 step -1 printNL(i)
Result: 4,3,2,1,0
For e in l loop was described when lists were concerned.
Goto:
x=1 goto et_1 x=2 et_1: #label name followed by colon
Functions and procedures
An example function definition:
int sum(int a, int b)= # = this is important !!!!!!!!! c=a+b return c
Equal sign must be used in function definition, this form is not popular but designing the language I had no better idea.
c=sum(a,b)
This assignment may serve as variable c declaration because the compiler knows sum type.
sum1(int a, int b)= c=a+b return c
Here function type is resolved from return expression type.
Functiuon names may be overloaded:
int sum(int a, int b, int c)= d=a+b+c return d
Defining functions recursion may be used.
int factorial(int i)= x=1 if i>0 : x=factorial(i-1);x=x*i return x
Procedures are similar:
proc printNL(Class_1 class_1) #equal sign my be used but is not necsessairy text="Class_1 :attr_int=" + toStr(class_1.attr_int) text=text + ", attr_str=" + class_1.attr_str printNL(text)
Global variables
Global variables are declared using global instruction, all must be same type.
proc incr_g1()
global g1; int g1
g1=g1+1
printNL("global g1 is="+toStr(g1))
global g1
g1=12
incr_g1()
# now g1 is 13
Keyboard and console
keyboard_text=getLine()
When you press enter key this ends and getLine returns entered chars as str.
As semi debug utility bp() procedure may be used that waits till you press enter key.
File io
To use file io procedures we can use following schema:
file_handler=OpenFile(file_name,type) ok=writeLines(file_handler, list_of_strings) or read_lines_output=readLines(file_handler) closeFile(file_handler)
Where openFile type is OF_READ, OF_WRITE or OF_CREATE_OVERWRITE
lines=[]
file_name="c:\\fsp_2007\\tut3_lines.txt"
#in strings \ before special kode so here two \\ are required
file_handler=openFile("c:\\fsp_2007\\tut4_lines.txt", OF_CREATE_OVERWRITE)
ok=writeLines(file_name,lines)
if ok=1 :printNL("write ok")
else:et=getLastError();printNL("write error "+et)
Than read it back from file
OkAndData read_lines_output
class OkAndData
ok=0
data=[]
read_lines_output=readLines(file_handler)
if read_lines_output.ok
printNL("read lines ok")
lines_read=read_lines_output.data
print(lines_read)
else:et=getLastError();printNL("read error "+et)
More advanced example
class CirclePoint
x=0;y=0;ok=0
proc printCircle(int x0, int y0, int r)=
maxX=80; maxY=40;col_no=0
if x0<0 or y0 < 0 or r <0 : printNL("wrong input data"); return
if x0+r>maxX or y0+r > maxY :print("wrong input data"); return
screen=[]
line=[CirclePoint]
#prepare list o lists containing points
for line_no=0 to maxY
line=new list #we need new list object
for col_no=0 to maxX
point=new CirclePoint #new point for each line,column
point.x=col_no; point.y=line_no
x=point.x; y=point.y*3/2; y00=y0*3/2
d2=(x-x0)*(x-x0)+(y-y00)*(y-y00)
if d2 <= r*r: point.ok=1 # ok=1 if point belongs to circle
append(line,point)
append(screen,line)
printNL()
for l in screen
line=l
#necsesary because compiler knows l as list not list of CirclePoint
for p in line
if p.ok=1:print("*")
else:print(" ")
printNL()
Using assembler code
To move data between int, byte variables and processors registers mov instruction is used. We can also define auxiliary procedures and call them directly using call instruction. To see registers printRegs auxiliary procedure may be used. When we have more assembler instruction $asm directive is used to start and end assembler code area.
i=12 mov eax,i asm mov ebx,eax asm mov ecx,eax asm add ecx,12 call printRegs mov i,ecx printNL(i) i=12 mov eax,i $asm #start assembler code mov ebx,eax mov ecx,eax add ecx,12 call printRegs $asm #assembler code end mov i,ecx printNL(i)
Result is:
0000000C 0000000C 00000018 00DB6690 : eax, ebx, ecx,edx 00DB6690 00DB0020 00E9F65C 0012FFA4 : ebp, esi, edi, esp 24
Auxiliary assembler procedure:
aux asm_proc() $asm add eax,ebx inc eax asm mov eax,1; asm mov ebx,2 call asm_proc
C language integration
In Ala we have some additional types to make C language integration easier. First is cstr – null ended text. Next are cstruct and cunion. To call function from external dll use call dll_name.function_name instruction. If function uses Pascal parameter passing convention (like win32 function do) it is possible to write dll_name.fun_name(parameters). Here is an example where actual parameters are put on stack by hand:
asm push 0 asm mov eax,ebp; asm sub eax,4; asm push eax #address of dummy variable asm mov eax,ebx;call lenCstr;asm push eax #length asm push ebx#address asm push F5h #-11 call KERNEL32.GetStdHandle asm push eax call KERNEL32.WriteFile
En example of parameters list:
call kernel32.ReadFile(hFile, lpBuffer, numberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped)
Class typeId
Class typeId is an integer number :
CirclePoint circlePoint i1=CirclePoint.typeId #class_name.typeId i2=typeId(circlePoint) #typeId is function returning variable typeId i3=typeId(i1) #integer variable type id is 2, str is 3 , list 4
Addr, ref functions
Addr function returns address of a variable , ref functions returns address of an object referenced by parameter variable.
addr_value=addr(i1) addr_value=addr(circlePoint) ref_value=ref(circlePoint)
Microsoft Win32 GUI programming
At the beginning I am going to describe program from window1.ala file. Resulting program creates window with one button. First we need to load necessary gui classes from module gui.
import gui from gui_win32.ala
Than we design class containing object window based on Window class and one control button_new based on Button class. Window component gives us access to window properties and methods.
class MainWindow Window window Button button_new
At the beginning of MainWindow constructor Window constructor is called. Coordinates x, y refer to left, upper corner of the screen, w is window width and h is window height. Next we have window style, mode that will be described later, parentId where 0 means no parent window and window title at he end.
After constructor call window id contains handler (window handle) to the window.
GetClientRect returns Rect (x,y,w,h) object where x,y coordinates refer to upper left corner of window client area. It is required because button will be located at the bottom of the window.
Button constructor Button(int x,int y,int w,int h,int style, str title, int parentId), as parentId we use window.id.
MainWindow MainWindow(int x, int y, int w, int h, str title)= MainWindow main_window main_window.window=Window(x,y,w,h,0,0,0,title) client_rect=getClientRect(main_window.window) button_h=20;button_w=100 button_x=10;button_y=client_rect.h-button_h-10 main_window.button_new=Button(button_x, button_y, button_w, button_h, 0, "new", main_window.window.id) return main_window
Now I will describe main program, first line sets two global variables hInstance and guiFont.
initGui() #!!!!!!!!!!!!!!!!!!
Next line calls MainWindow constructor, CW_USEDEFAULT constant means that we use operating system default values.
main_window=MainWindow(CW_USEDEFAULT,0,CW_USEDEFAULT,0, "window Ala")
Now we must show the window:
show(main_window.window)
At the end typical window message loop:
eventLoop()
As window style all win32 window styles may be used.
As far as the mode is concerned 0 means WINDOW_MODE_PARALLEL (modeless window : does not disable his parent window) ,
1 WINDOW_MODE_EXCLUSIVE alone (other windows are disabled).
In window2.ala example we draw simple figures.
First in window constructor we bind WM_PAINT message with windowPaint procedure.
MainWindow MainWindow(int x, int y, int w, int h, str title)= bind main_window.window, WM_PAINT,windowPaint(main_window)
Then at the begin of windowPaint procedure user32.BeginPaint procedure is called to start drawing, this procedure returns handle to drawing device context. At the end user32.EndPaint is called.
proc windowPaint(MainWindow main_window)= PAINTSTRUCT ps; hdc=0 call user32.BeginPaint(main_window.window.id,ps); mov hdc,eax drawHouse(hdc,100,100) #!!!!!!!!! call user32.EndPaint(main_window.window.id,ps) proc drawHouse(int hdc, int x0,int y0)= dd=100 #draw main wall x1=x0+dd;y1=y0+dd;w=dd+dd;h=w; color=GREEN drawRect(hdc,x1,y1,w,h,color,color) ddX=w/7;ddY=h/6 x2=x1+ddX; y2=y1+2*ddY; w2=2*ddX;h2=2*ddY ;color=WHITE;colorBorder=BLACK drawRect(hdc,x2,y2,w2,h2,color,colorBorder) x=x2+ddX; drawVerLine(hdc,x,y2,h2,colorBorder) y=y2+ddY; drawHorLine(hdc,x2,y,w2,colorBorder) x3=x1+4*ddX; y3=y2; w3=2*ddX;h3=4*ddY;color=BLUE drawRect(hdc,x3,y3,w3,h3,color,color) #draw the roof xr=x1;yr=y0;wr=2*dd;hr=dd;color=RED drawRect(hdc,xr,yr,wr,hr,color,color) for dy=1 to hr-1 dx=dy*2/3 x=xr-dx; y=yr+dy drawHorLine(hdc,x,y,dx,color) xr=xr+wr for dy=1 to hr-1 dx=dy*2/3 x=xr; y=yr+dy drawHorLine(hdc,x,y,dx,color) #draw a text on main wall x=x1+10;y=y1+10 drawText(hdc,x,y,"Alice has a cat",color)
File window3.ala contains two important lines. First line binds button, mouse left button click event and whenButtonNewClicked procedure.
MainWindow MainWindow(int x, int y, int w, int h, str title)= bind main_window.button_new, WM_LBUTTONDOWN, whenButtonNewClicked(main_window)
Second line sends WM_PAINT message to the window through draw() procedure.
proc whenButtonNewClicked(MainWindow main_window) draw(main_window.window)
Now window controls and window4.ala example.
control_x=20;control_y=20;control_w=200;control_h=20 main_window.text_box=TextBox(control_x, control_y, control_w, control_h,0, "" ,main_window.window.id)
Control x,y referes to left,upper corner of window client area, and main_window.window.id is a parent window id.
For ListBox and ComboBox :
addItem(main_window.list_box,item_text)
For RadioButtons to start new group – seting one element resets the others – WS_GROUP style must be used.
main_window.radio_button_1=RadioButton(control_x, control_y, control_w,control_h, WS_GROUP,"radio button 1",main_window.window.id)
As far as the |TreeList is concerned wen we add element, new element handle is returned, and this handle is to be used to add subelements:
master_item_id=addItem(main_window.tree_list,item_text) subitem_id=addItem(main_window.tree_list, master_item_id, item_text)
To read control data:
- TextBox and ComboBox :getText(..)
- ListBox:getCurSel(..) returns index of selected item
- CheckBox and RadioButton: getCheck(..) returns 1 if control is checked
MainWindow MainWindow(int x, int y, int w, int h, str title)=
MainWindow main_window
main_window.window=Window(x,y,w,h,0,0,0,title)
client_rect=getClientRect(main_window.window)
#textBox
control_x=20;control_y=20;control_w=200;control_h=20
main_window.text_box=TextBox(control_x, control_y, control_w,control_h,0,"",main_window.window.id)
#ListBox
control_x=300;control_y=20;control_w=200;control_h=300
main_window.list_box=ListBox(control_x, control_y, control_w,control_h, 0,"",main_window.window.id)
for i= 1 to 30
item_text="Alice "+toStr(i)
addItem(main_window.list_box,item_text)
#combobox
control_x=550;control_y=20;control_w=200;control_h=200
main_window.combo_box=ComboBox(control_x, control_y,control_w, control_h, 0,"",main_window.window.id)
for i= 1 to 5
item_text="Alice has a cat "+toStr(i)
addItem(main_window.combo_box,item_text)
#checkBox
control_x=20;control_y=300;control_w=200;control_h=20
main_window.check_box=CheckBox(control_x, control_y,control_w, control_h,0,"check box",main_window.window.id)
setCheck(main_window.radio_button_1,1) #!!!!!!!!!!!!!!!
#static
control_x=200;control_y=300;control_w=100;control_h=20
main_window.static=Static(control_x, control_y, control_w,control_h,0,"Static 1",main_window.window.id)
#group of radio buttons
control_x=20;control_y=350;control_w=200;control_h=20
main_window.radio_button_1=RadioButton(control_x, control_y,control_w, control_h,WS_GROUP, "radio button 1",main_window.window.id)
control_x=20;control_y=350+20;control_w=200;control_h=20
main_window.radio_button_2=RadioButton(control_x, control_y,control_w, control_h,0,"radio button 2",main_window.window.id)
control_x=20;control_y=350+40;control_w=200;control_h=20
main_window.radio_button_3=RadioButton(control_x, control_y,control_w, control_h,0,"radio button 3",main_window.window.id)
#next group of radio buttons style=WS_GROUP starts new group
control_x=20;control_y=420;control_w=200;control_h=20
main_window.radio_button_a=RadioButton(control_x, control_y,control_w, control_h,WS_GROUP,"radio button a",main_window.window.id)
control_x=20;control_y=420+20;control_w=200;control_h=20
main_window.radio_button_b=RadioButton(control_x, control_y,control_w, control_h,0,"radio button b",main_window.window.id)
control_x=20;control_y=420+40;control_w=200;control_h=20
main_window.radio_button_c=RadioButton(control_x, control_y,control_w, control_h,0,"radio button c",main_window.window.id)
#Tree list
control_x=300;control_y=350;control_w=200;control_h=300
main_window.tree_list=TreeList(control_x, control_y,control_w,control_h,0,"",main_window.window.id)
for i= 1 to 30
item_text="Alice in tree "+toStr(i)
master_item_id=addItem(main_window.tree_list,item_text)
item_text="leaf in tree "+toStr(i)
subitem_id=addItem(main_window.tree_list,master_item_id,item_text)
#push button
button_h=20;button_w=100
button_x=10;button_y=client_rect.h-button_h-10
main_window.button_new=Button(button_x, button_y, button_w,button_h,0,"new",main_window.window.id)
bind main_window.button_new, WM_LBUTTONDOWN,whenButtonNewClicked(main_window)
return main_window
proc whenButtonNewClicked(MainWindow main_window)
check_box_state=getCheck(main_window.check_box)
printNL("button clicked: checkBox check is " +
toStr(check_box_state))
radio_state=getCheck(main_window.radio_button_1)
printNL("radio button 1 state is "+toStr(radio_state))
radio_state=getCheck(main_window.radio_button_a)
printNL("radio button a state is "+toStr(radio_state))
printNL("text box text is:"+getText(main_window.text_box))
printNL("list box selected item index="+
toStr( getCurSel(main_window.list_box)))
printNL("combo box text is:"+getText(main_window.combo_box))
setCheck(main_window.check_box,0)
And here is the result:
Window5.ala is an example where we will build advanced gui element and where virtual functions will be used.
Our component DbGrid will consist of two controls:
class DbGrid Control dataWnd Control header
First is a window where rows of data are displeyed and the second is a header. When data rows are to be painted, getField function is called.
proc paintData(DbGrid dbg)= text=getField(dbg,lineNo,colNo)
This function is declared as virtual:
str getField(DbGrid dbGrid, int row, int col)=virtual
When component is used we must bind virtual function with real function that will supply data.
str getFieldHere(int row, int col)= res="row="+toStr(row)+" col="+toStr(col) return res MainWindow MainWindow(int x, int y, int w, int h, str title)= virtual getField(main_window.data_grid, int, int) = getFieldHere( int, int)
The result :
Very similar result using two DataGrid components from gui module(window6.ala).
RDBMS interface
Nowadays relational databases are very important. As an example posgresql in tut6.ala is concerned.
import postSql from post_sql.ala conn_str = "hostaddr=169.254.64.211 dbname = test user=postgres password=postgres" conn=connectDB(conn_str)
In the following code table table_1 with two columns, will be used. First column id is int4 postgresql type and second column is text (text type). The following sql command may be used to create table:
create table table_1( id int4, text text)
Now code to connect to database test, execute insert command and execute select query.
id=0; text=""
if isConnectionOk(conn)
sql="insert into table_1(id,text) values(4,'Alice has a cat')"
execSqlCmd(conn,sql) #execute sql command
sql="select id,text from table_1"
query_res=execSql(conn,sql) #execute sql query
rows=countSqlRows(query_res)
checkIfEquivalent(typeId(id),query_res,0) #is id field int
checkIfEquivalent(typeId(text),query_res,1) #is text field str
for i=0 to rows-1
printNL("row "+toStr(i));print(" ")
id=getIntField(query_res,0) #get field 0
print(id);print(" ")
text=getStrField(query_res,1) #get field 1
print(text)
nextQueryResultRow(query_res)
clearQueryResult(query_res)
closeDBConnection(conn) #!!!!!!!!!!!!!!!!!
else
s=connectionError(conn);printNL(s)
Modules
There are two reasons to use modules in Ala. First is code reuse. The example is gui module that contains classes and functions needed to create user interface.
import gui from gui_win32.ala
To use classes and procedures from imported module we do not need any prefix. The commpiler first tries to find class/function in current module, then in imported modules and in base module at the end. Secondly we use modules when program code become to large to handdle in one file espetially when compile time is concerned. When we first time compile after ide startup, base and imported modules are compiled and result is hold in memory. When we compile next time only modules from current file are compiled and base and imported modules executable code is added from memory.
Reference counting
There are two methods to handle objects: reference counting nad garbage collectors. Each method has its lights and shadows. One of the main aims to develop new programming language was performance, so I decided to use reference counting. Direct recursion in class designs is not allowed:
class Class_1 Class_2 attr_x class Class_2 Class_1 attr_x
However when we need recursion in class definition we can use all type:
class Class_1 all attr_all #here we can store Class_2 objects. class Class_2 Class_1 attr_x
Concurrent programming
When we have two or more processors( or cores) its possible to fork program execution. When done properly performance gain is very big and scales nearly in linear way when processors (cores) are added. Now two cores are common and in near future four and eight are expected. Current impementation of Ala uses one or two cores. To use second processor core first we mast set ide option mt to 1. Ater this exectutable code file name will change to xxx_mt.exe. Then in main code we must start second program thread:
initMT()
An example from tut7.ala:
for e in mainList ok1=e if isOdd(ind) setLen(ok1) con#!!!!!!!!!!!! else setLen(ok1)#!!!!!!!!!!!! waitThreadReady() ind=ind+1
The idea is to first call setLen example procedure concurently on second core , than for another element from list mainList to call example procedure on main core.
When main core procedure is finished than we wait untill concurrent procedure is over.
The output is:
elapsed time when concurrent 4.234 sec elapsed time on one core 7.704 sec
InitMT starts new thread. Calling function/procedure concurrently(con attribute) function address and parameters are passed to this thread. Compiler responsibility is to add alock and unlock operations when objects are created, global variables are used and print function is called.
Example code files
tut1.ala: int operations
tut2.ala: list operations
tut3.ala: if,while,for,functons and procedures, globals
tut4.ala: console and file io
tut5.ala: more advanced function example and assembler code integration
tut6.ala: postgresql programing
tut7.ala: concurrent code
window1.ala: simple window
window2.ala: drawing
window3.ala: binding events
window4.ala: controls
window5.ala: making your own components
window6.ala: using DataGrid component