January 19, 2021, 02:28:53 am

News:

IonicWind Snippit Manager 2.xx Released!  Install it on a memory stick and take it with you!  With or without IWBasic!


Aurora Tutorial Part 1.

Started by Ionic Wind Support Team, March 22, 2006, 03:03:49 pm

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Ionic Wind Support Team

March 22, 2006, 03:03:49 pm Last Edit: September 10, 2006, 01:23:17 am by Paul Turley
-------------------------------------------------------------------------
Part 1.  Getting started with Aurora

The hardest part when learning a new programming language is knowing where to start.  This tutorial is aimed at the new Aurora user who has used other programming languages in the past.  In Part 1 we will go over basic program structure, variable types, console I/O functions, loops and conditional statements.

Our first step is to launch Aurora and create a new source file. Do so now by clicking on the File menu and selecting New -> Aurora Source File

Save this file to disk by clicking on the File menu and selecting Save As...  Choose and appropriate file name such as "Tutorial1.src".

Every Aurora program has a starting point, where your code begins execution, and that is a subroutine named main.  In this tutorial we will use the terms "subroutine" and "function" interchangeably since there is no difference between the two in Aurora.

The main subroutine must be visible to the outside world, and in Aurora this is done by using the GLOBAL keyword.  Type the following in the blank source file:

Code Select

global sub main( )
{
}


That is the beginning of any Aurora program. 

CONSOLE basics
The Windows console, or shell, provides text based input and output facilities. Aurora supports text based programming with the built in console library.  Add the following lines, in blue, to your source window.

global sub main( )
{
   print("Hello from Aurora");
   print("press any key to close");
   While GetKey() = "";
}

Now let's compile the program and test it out.  Click on the  Build menu and select Build Single.  This tells Aurora that you want to compile the visible source file, and it is the only source file for your program.  When the "Executable Options" dialog appears use the "Target" pulldown box to choose "Console EXE".  Check the box next to "Execute after creating" and finally click on the "Create" button.

If we did everything correctly you should see the console window appear with the text from the program printed in it.  Press any keyboard key to end the program.

The print function can output any data type to the console.  Print will automatically send a newline to the console unless you tell it not to by adding a comma to the list of items to output.  Add the following lines, in blue, to your source window.

global sub main( )
{
   print("Hello from Aurora");
   print("What is your name? ",);
   name = readln();
   print("Hello ", name, ", Nice to meet you");
   print("press any key to close");
   While GetKey() = "";
}

The readln function reads a line of text from the console and waits for a the return key to be pressed.  You can also read a single character from the keyboard using the GetKey function.  GetKey returns the pressed key in either Ascii or Raw format.  Raw format allows reading special keys such as the function keys.  Let's change out program to wait for the F6 key to be pressed before we close.  Change the following lines, in red, in your source code window:

global sub main( )
{
   print("hello from Aurora");
   print("What is your name? ",);
   name = readln();
   print("Hello ", name, ", Nice to meet you");
   print("press the F6 key to close");
   While GetKey(true) != "\x75";
}

Compile and run the program.  "\x75" creates a one byte string with the character 0x75 which is the virtual key code for F6.  A list of virtual key codes can be found at the end of this tutorial.

Now that we have the basics of console input and output we can move on to our next subject.  Variables.

Variables and intrinsic data types
A variable is a temporary storage location for information in your program.  Think of it as memory for the data used in your program. Variable names can be any identifier you choose as long as they do not conflict with any reserved word or constant.

Aurora supports local, source global and program global variables.  A local variable is a variable that is defined within a subroutine and is only accessable from within that subroutine.  A file global variable is a variable that is defined outside of a subroutine and is accessable by any subroutine located in that file.  A program global variable is a variable that is defined outside of a subroutine and marked with the GLOBAL keyword.  A program global variable can be accessed by any source module in your program.

An intrinsic data type is a fancy name for what built in variable types a language supports.  Aurora supports the following intrinsic types:

BYTE - Can store a single character or an integer number from 0 - 255 (if unsigned)
WORD - 2 bytes, An integer number from 0 - 65535 (if unsigned)
INT - 4 bytes, An integer number from  0 - 4294967295 (if unsigned)
INT64 - 8 bytes, An integer number from 0 - 18446744073709551615 (if unsigned)
FLOAT - A single precision floating point number.
DOUBLE - A Double precision floating point number.
POINTER - An address
STRING - A string of characters up to a length of 255
DSTRING - A dimensionable string that can contain any number of characters.
WSTRING - A unicode string up to a lnegth of 255 characters.
DWSTRING - A dimensionable unicode string that can contain any number of characters.

The keyword UNSIGNED can be used with the integer types BYTE, WORD, INT and INT64 to define an unsigned variable.  The default is a signed variable.

A variable can be defined by assignment, using the DEF statement, or using the type-name syntax.  Aurora supports the auto-definition of variables by default unless turned off by using

#autodefine "off"

Auto-definition creates and specifies a variable by assigning it a value.  In our example program the variable 'name' was created and defined by assigning the return from readln().  In this case it was a STRING variable.  Here are some examples of defining variables:

Code Select

a = 1; //defines an integer variable and assigns it the value of 1.
name = "John"; //defines a string variable and assigns it.
float flValue; //uses the type-name syntax to defined a FLOAT variable named "flValue".
def dbValue as DOUBLE; // uses the DEF statment to define a DOUBLE variable named "dbValue".


The auto-defintion of variables is a convenience but should not be relied upon if you are not sure what kind of variable will be created, or if you prefer to define all of your variables ahead of time.  Auto-definition of variables can also create subtle bugs that are hard to track down if you have local and global variables with the same name.

Let's change our program to use a few variables.  Add the lines, in blue, in the source code window.

global sub main( )
{
int age; //An integer variable
float money; //a floating point variable

   print("hello from Aurora");
   print("What is your name? ",);
   name = readln();
   print("Hello ", name, ", Nice to meet you");

   print("How old are you? ",);
   age = StrToNum(readln());
   print("How much do you make a year? $",);
   money = StrToNum(readln());
   print("So you are ",age," years old and make $",money);
   print("You need a raise");
   print();

   print("press the F6 key to close");
   While GetKey(true) != "\x75";
}

Compile and run the program to see the results.

The variables defined in the program are local variables.  They are only accessable within the 'main' subroutine. Which is the only subroutine in our program. It is important to remember that local variables will contain random information until they are assigned a value. Do not depend on the contents of any variable before they are assigned.  Aurora supports a single step definition and assignment using the type-name syntax:

Code Select

double dbValue = 1.04567;
string strName = "A tale of two computers";


Global variables cannot use the single step defintion and assignment.  A global variable exists outside of the path of executing code so the assignment will be ignored.

Arrays
Arrays of up to three dimensions are supported by Aurora.  An array of more than three dimensions can be created using memory allocated by the NEW function.  For this tutorial we will concentrate on normal arrays.  Defining an array is done with brackets:

Code Select

int myArray[10,100];
double prices[500];


Arrays are accessed by using brackets and an index to the array location.  Indices in Aurora are zero based, which means if you want to access the 10'th element of a dimension the index is 9:

current_price = prices[499];

Arrays can be initialized with a list of values, separated by commas.

Code Select

prices = 1.5, 300.50, 40.25, 77.99, 175.65, 199.33,
1000.99, 200.32, 123.55, 763.33 ;


Let's change out program and add an array.  We will also use a loop, which hasn't been covered yet, but keep following along until we get there.  Add the lines, in blue, in the source code window.

global sub main( )
{
int age; //An integer variable
float money; //a floating point variable
string colors[8]; //an array
   colors = "red", "blue", "green", "yellow",
         "purple", "burgandy", "orange", "violet";
   print("hello from Aurora");
   print("What is your name? ",);
   name = readln();
   print("Hello ", name, ", Nice to meet you");

   print("How old are you? ",);
   age = StrToNum(readln());
   print("How much do you make a year? $",);
   money = StrToNum(readln());
   print("So you are ",age," years old and make $",money);
   print("You need a raise");
   print();
   print("My favorite colors are: ",);
   for(x = 0; x< 8; x++)
   {
      print(colors[ x ]," ",);
   }
   print();print();

   print("press the F6 key to close");
   While GetKey(true) != "\x75";
}

In addition to intrinsic types Aurora allows creating new variable types through the use of the #typedef preprocessor command.  Using #typedef you can create aliases to types and new type names. It is common in programming to create aliased type names to represent a specific type of data and to improve readability.  There is no difference between using the original type and its alias. Examples:

#typedef UINT unsigned int
#typedef HWND UINT
#typedef HANDLE UINT
#typedef BOOL int
#typedef CHAR byte

HANDLE hFile = 0;
BOOL bDone = false;

Preprocessor commands such as #typedef, #include, #autodefine, etc. do not need a terminating semicolon as they do not represent code, but a command to the compiler itself.

User defined variable types
Aurora supports the creation of user defined variable types, or structures as they are commonly called.  A structure is a collection of variables tied together by name.  Each variable within a structure is called a member. The struct statement is used to begin the definition of a structure and the member variables are surrounded by braces.  An example structure:

struct FLOATRECT
{
   float left;
   float top;
   float width;
   float height;
}

The structure name becomes a new type of variable that you can define in your program:

FLOATRECT flRect;

When a structure variable is created, all of the member variables are created and accessed by using either the dot '.' or dereference '->' operators.  The dereference operator is used when you have a pointer to the structure.  Such as when it is created by NEW. As an example using the FLOATRECT structure above would look like:

flRect.left = 100.0;
flRect.top = 50.5;
flRect.width = 25.5;
flRect.height = 25.5;

While we haven't yet covered memory allocation with Aurora it is important to know the syntax of using pointers so a short example is warranted:

FLOATRECT *pRect = NEW(FLOATRECT,1);
pRect ->left = 100.0;
pRect ->top = 50.5;
pRect ->width = 25.5;
pRect ->height = 25.5;

Pointers and the dereference operator will be covered in more detail in Part 2 of this tutorial.

Constants, literals, and type modifiers
Constants are identifier to numeric or string mappings that cannot change value. Similar to variables in that they are referred to by name but don't actually use memory or generate any code. The compiler substitutes a constant identifier with its numeric or string identity at compile time. Aurora supports two constant defintion statements, CONST and #define.  Both statements can create numeric constants, #define can also create preprocessor definitons. Examples:

#define WM_USER 0x400
#define APP_CLEAN WM_USER+1
#define APP_MOVE WM_USER+2
#define FL_START 123.45f
#define APP_NAME "My Cool app"

CONST NumEntries = 100;

The CONST keyword is available as an aid to converting code from other languages. A preprocessor definition defines an identifier as either true or false and can be used to control whether code is compiled or not. 

#ifndef MYINCLUDE
#define MYINCLUDE
...code here
#endif

String literals
A string literal is text enclosed in quotes. The compiler supports string escape sequences to insert special ASCII values into the string, these special character are those that would be hard to type, or have special meaning such as the newline character. Consider the following examples:

Mystring = "This is a string";
// a string with embedded quotes
MyString2 = "This \"is\" a string";

All escape sequences begin with a single backslash ' \ '. In order to have a backslash in the string itself you need to use a double backslash ' \\ '. This is important to remember when working with filenames.

Myfile = "C:\\data\\inp.text";

Supported escape sequences:

Sequence  Inserted character

\n   Newline character
\t   TAB character
\\   A single backslash
\"   A quote
\xnn   An ASCII character whose hex value is 'nn'.

The last sequence deserves a bit of explanation. The standard ASCII character set is an integer number from 0 to 127.  Or in hexidecimal notation 0x00 to 0x7F.  The extended character set from 0x80 to 0xFF and the non printable characters from 0x00 to 0x31 are not normally available from a standard keyboard, or have special meanings to a text editor.  The \x sequence allows embedding these characters into the string. Example:

strOut = "\x1BThere is an <ESC> character at the beginning";

The maximum length of a string literal is 1023 bytes. If you need a longer string literal then append them together with the concatenation operator. String literals are stored directly in the executable file.

Duplicate string literals in a program will be combined into a single instance in the executable. This can cause a side effect if you assign a literal to a pointer and change that literal through dereferencing.  Always use a variable in this case.


Numeric literals
Numeric literals are sometimes called numeric constants. We use the term literals to differentiate them from the CONST keyword and to avoid confusion. A numeric literal is simply a number, either integer or floating point, entered directly into the source code. Such as when assigning to a variable:

A  = 1.3452; //The number is the literal

Direct entering of exponents is also allowed by the compiler

A = 1.2e-10; //Set a to equal 0.00000000012

Hexadecimal numbers may also be directly specified by using the 0x identifier

A = 0x500 + 0x2FFF;

Numeric modifiers
The compiler supports modifiers to the entered number to change its type. If a number is entered without a decimal point it is treated as an INT type (4 bytes) and with a decimal point as a DOUBLE type (8 bytes).  It is sometimes desirable to modify the type of the numeric to tell the compiler how to convert and store it. For example an INT literal is not large enough to hold an INT64 literal. Numeric modifiers appear as a single character following the literal.

//A will be defined as an INT64
//If not already defined
A = 36674965736284q;

//flNum will be defined as type FLOAT
//if not already defined
flNum = 1.234f;

Supported modifiers:

Modifier   Result
q   INT64
f    FLOAT
u   unsigned int

The u modifier was specifically designed for entering hexadecimal numbers as an unsigned quantity. Normally all non decimal numbers are treated as a signed integer which can cause calculation problems when using hexadecimal numbers.

A = 0xFFFFFFFFu -1u;

Without the u modifier A, if not already defined, would have a type of INT and a value of -2.  By specifying the u modifier A is defined as an unsigned integer with a value of 0xFFFFFFFE or 4294967294.

All about loops
Now that we have the basics of variables and constants down we can now move on to loops.  A loop is just what it sounds like, a portion of code that gets repeated a number of times based on a condition. Aurora supports three basic loop types.The FOR loop, the WHILE loop and the DO loop. 

The FOR loop will most likely be the most used construct in your program. Aurora's FOR loop is constructed by specifying three expressions. The initializer expression, the conditional expression and the loop expression.  The syntax of FOR looks like this:

for( init_expr; cond_expr; loop_expr)
{
     statements;
}

statement can be either a single program statement or multiple program statements enclosed in braces.
init_expr Executed once before the beginning of the loop.
cond_expr Before each loop begins this expression is executed. If it evaluated to TRUE then the loop continues, if FALSE the loop exits.
loop_expr Executed at the end of every loop.

It may sound complicated, but in reality the simplicity of it gives the FOR loop a lot of power.  Let's start a new program so we can experiment with the FOR loop.  Create a new source file and save it to disk as Tutorial1A.src or a name of your choosing. Type in the following short program:

global sub main( )
{
int count;
   for( count = 0 ; count < 20; count = count + 1)
   {
      print(count);
   }
   print();
   print("Count now equals: ", count);
   while GetKey() = "";
}

Compile an run the program to see the results.  The initializer expression of our FOR loop is count = 0 which stores the value of zero in our variable before the loop begins. the conditional expression is count < 20 which compares our variable to the number 20 before each loop begins.  If our variable is less than 20 then the loop continues.  Our loop expression is count = count + 1 which will add 1 to our variable at the end of each loop iteration.

Creating a loop that counts backwards is a simple matter of changing our conditional expression and loop expression.   Change the following line, in red, in your source code window:

global sub main( )
{
int count;
   for( count = 20 ; count > 0; count = count - 1)
   {
      print(count);
   }
   print();
   print("Count now equals: ", count);
   while GetKey() = "";
}

Compile and execute the program to see the difference. 

The FOR loop can use more than just numbers in its expressions. Any expression you can test a condition on can be used in the loop which makes it a very powerful tool. Consider the following loop:

string s;
int x=0;
for( s = "A"; s[ x ] < "Z"; x++)
{
   s[x+1] = 'B' + x;
}

Can you figure out what the loop does without running it?  Replace the loop in our program with the one above, and add a print(s); statement.  Your program should look like this:

global sub main( )
{
string s;
int x=0;
   for( s = "A"; s[ x ] < "Z"; x++)
   {
      s[x+1] = 'B' + x;
   }
   print(s);
   while GetKey() = "";
}

Next on the agenda is the the WHILE loop.  The WHILE loop repeats one or more statements while a condition is TRUE.  The condition is tested at the beginning of each loop.  To see how a WHILE loop operates let's change the program above as so:

global sub main( )
{
string s = "A";
int x=0;
   while( s[ x ] < "Z" )
   {
      s[x+1] = 'B' + x;
      x++;
   }
   print(s);
   while GetKey() = "";
}

As you can see both the FOR and WHILE loops continue iterating while a conditon is TRUE.  Last but not least is the DO loop.  The DO loop repeats one or more statements until a condition is true.  The condition is tested at the bottom of the loop so the statements will always execute at least once. 

Change the program as follows to see a DO loop in action:

global sub main( )
{
string s = "A";
int x=0;
   do
   {
      s[x+1] = 'B' + x;
      x++;
   } until( s[ x ]  = "Z" );
   print(s);
   while GetKey() = "";
}

Operators and mathematic expressions

An operator is a symbol that performs a specific function on a variable, constant or identifier. Operators consist of mathematic operators, conditional operators, Boolean operators and control operators.  Some symbols are reused for other purposes depending on the context of the operator. For example the & symbol is used as the bit wise AND operator and also as the Address Of operator


The Aurora compiler understands the following operators:

Symbol      meaning
+         Addition / String concatenation.
-         Subtraction / Unary minus.
*         Multiplication / Pointer dereferencing / Pointer definition.
/         Division.
%         Modulus.  Remainder of an integer division.
^         Raise to a power.
=         Assignment / Equality.
==         Equality.
|         Bitwise OR.
||         Logical OR.
&         Bitwise AND / Address Of.
&&         Logical AND.
XOR         Exclusive OR.
AND         Logical AND.
OR         Logical OR.
>>         Bit shift right.
<<         Bit shift left.
->         Pointer derference and member access to struct class.
.         Member access to struct/class
>         Greater than.
<         Less Than.
>=         Greater than or equal.
<=         Less than or equal.
!=          Not equal.
< >         Not equal.
!         Boolean NOT.

Operators are evaluated from left to right ordered by the following precedence rules.  The operators with the highest precedence level are evaluated first unless overriden with parenthesis.

Precedence level      Operators
1               &, |, AND, OR, &&, ||
2               >, <, >=, <=, !=, <>, =, ==, |=, &=, *=, /=, -=, +=
3               >>, <<
4               +, -
5               /, *, %, XOR
6               ^
7               (, )
8               ., ->


Conditional Statements

A conditional statement executes one or more program statements based on a condition. These include IF, ELSE, and SELECT (SWITCH). 

The IF statement

The IF statement will probably be your most used conditional statement. The statement has two forms, the block IF and a single line IF. The block form executes one or more statements if a condition is TRUE

Create a new source file and save it to disk as Tutorial1B.src or a name of your choosing. Type in the following short program:

global sub main( )
{
int num;
   do
   {
      print("Enter a number - 999 to quit ",);
      num = StrToNum(readln());
      if ( num != 999)
      {
         print("Answer = ", num + 2);

      }
   } until num = 999;
   print("Press any key to close");
   while GetKey() = "";
}

Aurora supports both the ELSE and ELSE IF keyword pairs.  When combined with an IF statement the statement(s) following an ELSE will execute if the condition is FALSE.  An ELSE IF statement will begin a new IF conditional when the previous condition was FALSE.

IF/ELSE statements may be nested.

For example:

Code Select

global sub main( )
{
int num;
do
{
print("Enter a number - 999 to quit ",);
num = StrToNum(readln());
if ( num != 999)
{
if(num < 100)
print("Answer = ", num + 2)
else if(num < 200)
print("Answer = ", num * 2)
else
print("Answer = ", num / 2);

}
} until num = 999;
print("Press any key to close");
while GetKey() = "";
}


As illustrated in the example above a single semicolon is used to terminate a group of single line IF and ELSE statements.  If you use the block form, by placing one or more statements in braces, you must terminate each line with a semicolon.

Code Select

global sub main( )
{
int num;
do
{
print("Enter a number - 999 to quit ",);
num = StrToNum(readln());
if ( num != 999)
{
if(num < 100)
{
print("Answer = ", num + 2);
}
else if(num < 200)
{
print("Answer = ", num * 2);
}
else
print("Answer = ", num / 2);

}
} until num = 999;
print("Press any key to close");
while GetKey() = "";
}


The SELECT (SWITCH) statement

The SELECT statement is a conditional statement that allows you to test a single expression against one or more conditions. A distinct CASE label is used for each condition. SWITCH is an alias for SELECT and is commonly used in the C language.

CASE statements can be grouped together by using the CASE& keyword. If none of the CASE comparisons are TRUE an optional DEFAULT statement can be specified as a catch all.

Each CASE, CASE& or DEFAULT statement must be terminated by a colon.

Type in the following short program to illustrate.:

Code Select

global sub main( )
{
byte c;
int done = 0;
print("press some keys, Q to quit");
do
{
do
{
c = GetKey();
} until c != 0;
select(c)
{
case 'A':
case& 'a':
print("You pressed \"A\"");
case 'Z':
case& 'z':
print("You pressed \"Z\"");
case 'Q':
case& 'q':
done = true;
default:
print("The ASCII value is ",c);

}
} until done;
print("Press any key to close");
while GetKey() = "";
}




Writing Subroutines

Subroutines are sections of programs that need to be executed numerous times. Subroutines can be called from anywhere in the program and return execution from where they were called. Functions, commands, statements, API, procedure, and handlers are all common names for a subroutine. The distinction of the name depends on the usage.

All subroutines must begin with the SUB or GLOBAL SUB keywords. The statements of a subroutine are enclosed in braces { }. The subroutine can appear anywhere in your source files and do not affect the path of execution until you call them. Subroutines do not need to be declared if they are only used in the source file they are defined in. You must declare a subroutine as external if you wish to use a subroutine located in another source file, this will be covered shortly.

In the SUB statement list any parameters that will be passed to the subroutine and also whether or not your subroutine returns a value. Here is a short example subroutine

Code Select

SUB MyFirstSub( int num ), int
{
    INT temp;
    temp = num * 4;
    RETURN temp;
}


The variables defined in your subroutine, or listed as a parameter in the SUB statement are called local variables. A local variable is only accessible while your subroutine is being executed. Once control is returned to the code that called the subroutine the local variables do not exist. All local variables are stored in a special area of memory called the stack.

The stack size is set by the 'Advanced' tab on the Executable Options dialog or the Project Options dialog.  The default stack commit size is set to 32K and indicates how large the size of the local variables in any one subroutine can be.  If you exceed this size the compiler will generate a warning message so you can adjust as needed.

A RETURN statement is only needed if you are returning a value.  Or if you wish to return from your subroutine at a point in the code other than the end of the subroutine. The compiler automatically inserts the RETURN statement at the end.  You will get a warning message if you forget a RETURN statment in a subroutine that returns a value.

Calling the subroutine

A subroutine is called, or jumped to, by simply using its name and specifying the parameter values to pass. If a subroutine does not have any parameters you must still use an empty set of parenthesis ( ).  A subroutine that returns a value can be used anywhere you would use that type of value.

Type in and run this example program to illustrate subroutines:

Code Select

global sub main( )
{
Cls();
CenterPrint("Subroutine Example",1);
CenterPrint("Today Is: "+DateTime(),6);
CenterPrint("Press any key to close",11);
WaitForKey();
}

SUB WaitForKey()
{
while GetKey() == "";
}

SUB CenterPrint(string text,int row)
{
Locate(row, (80 - len(text)) / 2);
print(text);
}

SUB DateTime(),string
{
string strReturn;
strReturn = FormatDate() + " " + FormatTime();
return strReturn;
}



Passing parameters

The above example shows passing values to a subroutine. Variables can be passed by either value or reference depending on the type of variable and whether or not the BYREF or BYVAL keyword is used.  When a variable is passed by reference the subroutine is allowed to make changes to the contents of the passed variable. When a variable is passed by value a copy of the contents of the variable is sent to the subroutine and the original variable will remain unchanged.

Aurora defaults to passing by value for all numeric types and passing by reference for strings,structures, and arrays. The following table lists the defaults:

Type      Default passed by
BYTE         Value
WORD         Value
INT         Value
FLOAT         Value
DOUBLE      Value
INT64         Value
STRING         Reference
WSTRING      Reference
POINTER      Reference
Structure      Reference*
Array         Reference
Class/Interface         Reference

*Structures can be passed by value by using the BYVAL keyword.
Numeric types can be passed by reference using the BYREF keyword or using a pointer to type.

Passing Arrays

Arrays require special handling when passing them to a subroutine. The subroutine needs to know the dimensions of the array at compile time. For single dimensioned arrays it is not necessary to supply a dimension and an empty bracket set will suffice '[ ]', but it is good procatice to specify even the single dimenstion. All arrays are passed by reference so anything done to the array in the subroutine will modify the array passed to it.

This example illustrates passing arrays to a subroutine:

Code Select

global sub main
{
int myarray[10,50];
float floatarray[10];

ArraysAreFun(myarray, floatarray);
for(int i=0;i< 10;i++)
{
for(int j=0;j<50;j++)
{
print(myArray[i,j],",",);
}
}
print("\n");
for(i=0;i<10;i++)
{
print(floatarray[i]," ",);
}
print();
while GetKey() == "";
}

SUB ArraysAreFun(int iArray[10,50], float flArray[10])
{
for(int i=0;i< 10;i++)
{
for(int j=0;j<50;j++)
{
iArray[i,j] = i*50+j;
}
}
for(i=0;i<10;i++)
{
flArray[i] = fsind(i);
}
}


Optional Parameters
Optional parameters at the end of the parameter list may be specified using the OPT keyword. A default value may be specified and will be used if the parameter is not included in the calling list. If a default parameter is not included then either 0 or a NULL string will be passed to fill in the optional parameter.

Code Select

global sub main()
{
string temp;
print("Hello " + GetString("Enter Your Name: "));
print("Press return to close");
temp = GetString();
}

SUB GetString(opt string *prompt),string
{
if(prompt)
print(*prompt,);
return readln();
}


Declaring subroutines
The DECLARE statement is used to tell the compiler about a subroutine that might be located in another file, in a DLL, or using a different calling convention such as CDECL (C declaration).  When a global subroutine is compiled in another source file you can use it in any other source file, in the same project, by using the DECLARE EXTERN keywords.

A global subroutine is one that has been created with the GLOBAL SUB keyword pair.  This allows the subroutine to be listed in a special table used by the linker.  When the linker sees a call to an external subroutine it looks up the address of that subroutine in the table and resolves the reference to it.  To really see global subroutines in action you need to create a project, add two or more source files, and create a few global functions to test.

Code Select

//file1.src
declare extern SomeSub(int a),int;
declare extern AnotherSub(string s),string;

global sub main()
{
print(SomeSub(10));
print(AnotherSub("Hello There"));
print("Press any key to close"));
while GetKey() == "";
}

-------------------------------
//file2.src
global SUB SomeSub(int a),int
{
return a * 2;
}

global SUB AnotherSub(string s),string;
{
return s + ", nice to meet you";
}


All global subroutines must be uniquely named or linking your executable will fail with a "duplicate definition error". When debugging only subroutines declared as global will display in the context window when a breakpoint is encountered. You will still get the correct file name and line number, just the name of the subroutine will default to the last global name in the path of execution.

You can also use the DECLARE statement for subroutines located in the same file, but it is not necessary with Aurora. When the DECLARE statement is used locally it will override the parameter names specified in the SUB statement so be sure to name them the same to avoid confusion.

Calling conventions
A calling convention controls how the compiler calls a subroutine, how the parameters are pushed, and how return values are handled.  By default Aurora uses the STDCALL calling convention which is standard in many other languages. As mentioned previously you can also use the CDECL calling convention which is standard with the C language. CDECL allows creating subroutines with a variable number of parameters.  The 'sprintf' function in the C runtime library is a good example of a subroutine that takes a variable number of parameters and must be declared using the CDECL keyword before it can be used.

A short example:

Code Select

DECLARE CDECL import,sprintf(out as STRING,format as STRING,...),INT;

global sub main()
{
string buffer;
sprintf(buffer, "%s 0x%X\n", "The answer in hexidecimal is:", 32766);
print(buffer);
print("Press any key to close");
while GetKey() == "";
}


Creating a variable argument subroutine
The previous example illustrated how you would declare and call a subroutine that accepts a variable number of arguments.  You can write a subroutine that accepts a variable number of arguments as well.

The ellipses (...) when used as the last parameter of a subroutine indicates to the compiler that it can accept any number of arguments. The subroutine must have at least one parameter before the ellipses.  Aurora automatically uses the CDECL convention when it see the ellipses so specifying it in a declare statement is unneccessary.

SUB AddItUp(int num, ...)

To get to the variable argument list use the VA_START function with the parameter preceeding the ellipses.  VA_START returns a pointer to the passed arguments on the stack and it must be the first statement in your subroutine.

void *args = VA_START(num);

The variable arguments are passed without regard to type and size information. It is up to the programmer to determine the size of each argument sent to the subroutine. A common methods is to use an index variable or formatting string. For example The USING function, and the C sprintf function, use a variable number or arguments whose type and size depends on a formatting string.

The size of a type can be determined using the LEN operator. 

//get the first argument
arg1 = *(double)args;
args += len(DOUBLE);
//get the second argument
arg2 = *(int)args;

It is important to remember that how you access an argument is entirely up to your design. By default all numeric variable types are passed by value. To keep inline with industry standards all floating point types are converted to a DOUBLE before being passed. Strings and structures are passed by reference.

This example illustrates creating a subroutine that accepts a variable number of arguments:

Code Select

global sub main()
{
//Call the variable argument subroutine
//Add up 4 numbers
print( "Sum of 20.5, 10.0, 40.32, 60.1" );
print( AddItUp(4, 20.5,10.0,40.32,60.1),"\n" );
//Add up 7 numbers
print( "Sum of 1.32, 5.0, 1.0, 10.7, 100.74, 845.25, 721.11" );
print( AddItUp(7, 1.32,5,1,10.7,100.74,845.25,721.11),"\n");

//Wait until closed
print("Press any key to end");
while GetKey() == "";
}

SUB AddItUp(int num,...),double
{
void *pArgs;
double flTemp = 0.0f;
//get a pointer to the first unknown argument
pArgs = VA_START(num);
//Add up all variable arguments
for(int x=0;x < num; x++)
{
flTemp += *(double)pArgs;
//A DOUBLE precision number is 8 bytes long
pArgs += len(DOUBLE);
}
//return the sum
RETURN flTemp;
}


Function pointers
Aurora supports function pointers, which allows indirectly calling a subroutine through the use of a special pointer.  The function pointer is created with the declare statement and can be assigned any address.

DECLARE *SomeFunction(int a, float b),int;

The name of the function is not important as it will be used to hold the address of a subroutine.  The parameters must match the referenced function.

After a function pointer is initialized it can be used like any other subroutine.  A common use for function pointers is dynamically loading a DLL and calling a function at run time.  Using this method the case of a missing DLL file can be caught by your program and an appropriate error message, or return value, given.

Aurora uses this method in the database library:

Code Select

DECLARE *SQLConfigDataSource(hwndParent as UINT,fRequest as WORD,lpszDriver as STRING,lpszAttributes as STRING),INT;
CDatabase::CreateMDB(path as STRING),INT
{
def hlib as unsigned INT;
def hfunction as unsigned INT;
def szAttrib as STRING;
INT rc = FALSE;
ZeroMemory(szAttrib,255);
szDriver = "Microsoft Access Driver (*.mdb)";
szAttrib = "CREATE_DB=\""+path+"\" General\x0\x0";
if(LEN(path))
{
hlib = LoadLibraryA("odbccp32.dll");
if hlib
{
SQLConfigDataSource = GetProcAddress(hlib,"SQLConfigDataSource");
if SQLConfigDataSource
rc = SQLConfigDataSource(NULL,ODBC_ADD_DSN,szDriver,szAttrib);
FreeLibrary(hlib);
}
}
RETURN rc;
}


Function pointers can also be used with your own subroutines.  This example uses function pointers to create a four function calculator. You could do the same thing without function pointers of course, however a comprehensive example is difficult to come up with in a small program. 

Code Select

//a function pointer
DECLARE *Operation(float p1, float p2),float;

global sub main()
{
//an array to hold subroutine addresses
UINT fnArray[4];
fnArray = &Addition,&Subtract,&Multiply,&Divide;
//variable to hold input
string calc;
//variables to hold operands
float p1,p2;
//Operator index into the funciton array
int op;

Cls();
print( "Simple Calculator" );
print( "-----------------" );
print( "Enter a calculation and press <ENTER>" );
print( "Example: 24 + 23" );
print( "Enter Q by itself to quit" );
DO
{
op = -1;
print(">",);calc = readln();
//first operand is easy, VAL stops at the first invalid character
p1 = VAL(TrimLeft(calc));
//go through the string to find the operator
i = 0;
while calc[i] && op = -1
{
select calc[i]
{
case "+":
op = 0;
case "-":
op = 1;
case "*":
op = 2;
case "/":
op = 3;
}
i++;
}
//did we find a valid operator?
if op = -1
print( "Invalid operator, Try Again" )
else
{
//we found something to calculate
//The next operand is after the operator
p2 = VAL(TrimLeft(StrMid(calc,i+1)));
//check for division by 0
if op = 3 && p2 = 0.0f
print( "ERROR: Attempted division by 0" )
else
{
//perform the calculation using an indirect function call
Operation = fnArray[op];
result =  Operation(p1,p2);
print( result );
}
}
}
until calc = "Q" || calc = "q";
}

//Define the subroutines to be called indirectly
SUB Addition(float p1,float p2),float
{
return p1 + p2;
}

SUB Subtract(float p1,float p2),float
{
return p1 - p2;
}

SUB Multiply(float p1,float p2),float
{
return p1 * p2;
}

SUB Divide(float p1,float p2),float
{
return p1 / p2;
}


-------------------------------------------------------------------------

This ends Part 1 of the tutorial.  Part 2 will be started in a separate topic.
Ionic Wind Support Team

mrainey

Software For Metalworking
http://closetolerancesoftware.com

Brian

Paul,

Can we download this tutorial from somewhere?

Brian

Ionic Wind Support Team

Not done with it yet ;).  You can print it right from the forum by clicking on the 'print' button at the top of the post.
Ionic Wind Support Team

Ionic Wind Support Team

Ionic Wind Support Team

John Syl.

Not wishing to extend this thread much more than necessary, I just want to say that this tutorial is just the job.
I have converted the tutorial so far into a works document AND pdf file, mainly because if I do this I Have to read it and
it sticks better.  As my contribution to this wonderful forum and language, and with Pauls agreement I will make the pdf available.

regards
John
Intel 3.6 p4 ht, XP home,2 gb mem, 400 gb hd 20gb raid 0, Nvidia 6600le.
AMD k6-2 500, 40gb.

Started on PDP11 Assembler, BASIC, GWBASIC, 6502, Z80, 80x86, Java, Pascal, C, C++, 
IBasic (std & pro), Aurora, EBasic.  (Master of none, but it's been fun!)

Ionic Wind Support Team

John,
Thanks.  And yes you can make a pdf available ;)
Ionic Wind Support Team

Ionic Wind Support Team

Ionic Wind Support Team

kryton9

Ok, I went through the online help, downloaded the nice pdf format tutorial, but I have no clue how you guys went from reading those to writing what you are writing with classes and such?

What can I do next if anything to learn how to do anything more advanced. I know we are in an alpha version, so I am not complaining at all. Just wanted to know if I missed anything?

I don't want to flood the forums with questions if I can take the time and read up myself. Did everyone get so advanced with Aurora by using the source files?


Parker

Mostly from a C++ book, I believe it was called "Teach yourself C++ in 24 hours", from IBasic, and from google.

Bruce Peaslee

Quote from: kryton9 on May 14, 2006, 08:23:19 pm
Ok, I went through the online help, downloaded the nice pdf format tutorial, but I have no clue how you guys went from reading those to writing what you are writing with classes and such?

What can I do next if anything to learn how to do anything more advanced. I know we are in an alpha version, so I am not complaining at all. Just wanted to know if I missed anything?

I don't want to flood the forums with questions if I can take the time and read up myself. Did everyone get so advanced with Aurora by using the source files?


I wrote whatever I wrote before these tools became available, but I was here close to the beginning. I'm not sure what you mean by "more advanced". Classes gave me a little trouble, but I have tried to work my way through their use.

It seems I ask one question for evey ten lines of code. Nobody seems to mind. That's why we're here.
Bruce Peaslee
"Born too loose."
iTired (There's a nap for that.)
Well, I headed for Las Vegas
Only made it out to Needles

Ionic Wind Support Team

Ionic Wind Support Team

Ionic Wind Support Team

Ionic Wind Support Team

Ionic Wind Support Team

Ionic Wind Support Team

Ionic Wind Support Team

The only thing that really annoys me about SMF is code blocks don't keep the indentation of lines when cutting and pasting.  Also when printing.  I will be posting an html version of the tutorial as well.
Ionic Wind Support Team

John S

John Siino, Advanced Engineering Services and Software

Haim

A real eye opener!
Thanks for a great tutorial!

Haim

John S

June 05, 2006, 01:26:18 am #17 Last Edit: June 05, 2006, 02:48:53 am by John S
Paul,
I think I spotted one of the global main statements without the ().
I fixed a couple spelling errors and attempted to format the coding to fit in the pages.

Here's my attempt to craft a doc and pdf.  I used Courier for the Code and Arial for the Text.  Had to split and reduce the font size of the last code example to fit on pages in frames.

I saved the doc file in Word 97 format.   Feel free to edit.
John Siino, Advanced Engineering Services and Software

JR

Quote
The only thing that really annoys me about SMF is code blocks don't keep the indentation of lines when cutting and pasting.

It keeps it if you use spaces instead of tabs. Always have hated these C++ code files full of tabs that become awful when you load them with a different editor.

Parker

I think it's a problem with the gecko engine which netscape and mozilla use, because with IE it copies fine (I use firefox though, so I have the same problem).

Rock Ridge Farm (Larry)

The same problem exist with anything I download from Aurora. No tabs in firefox and looks OK in MS sploder.
I like firefox so I just adapt and overcome.

Doc

Quote from: Rock Ridge Farm (Larry) on June 06, 2006, 06:31:07 am
The same problem exist with anything I download from Aurora. No tabs in firefox and looks OK in MS sploder.
I like firefox so I just adapt and overcome.



Kind of off topic... well, maybe not...
If you prefer Firefox, but have difficulties with a few pages ocassionally, give this F.F extension a try:
https://addons.mozilla.org/firefox/1419/

Works extremely well.

-Doc-

REDEBOLT

Paul,

Thanks for a great tutorial!
I noticed though, when selecting text in the Aurora IDE, it does'nt do a reverse color so I can see what's selected.
Is there something I need to toggle somewhere so it does?

Regards,
Bob
Regards,
Bob

Ionic Wind Support Team

Works fine here.  Did you select an odd background color?  The Scintilla DLL uses a grey background as the default selection color so it will be invisible if you also choose that color.

Ionic Wind Support Team

REDEBOLT

Yup. That's what I did.  Switched to light green background and all is well.
Regards,
Bob