A Beginners Guide To Euphoria

Creating Library Routines And Variable Types

The library routines and variable types in Euphoria should be more than enough for anyone to write full-featured programs. However, some exceptions may arise where the programmer needs to design custom library routines and variable types. In addition, custom library routines can also help a programmer design programs in a modular manner, organizing very large programs into easily identifiable sections of code that the programmer can keep better track of. Another advantage of using custom library routines is that they can be used in other programs without having to re-invent the wheel every time you write a new program. Also, you can insert library routines created by other Euphoria programmers into your program. Custom library routines save time and effort in software creation!

You will remember from our introduction to library routines that library routines are either procedures or functions. As a result, you can either create a function or a procedure. Any library routines you create must be declared in the program, just like variables, before being used. When declaring a custom library routine, you must state the following:

   1) Whether the library routine is a procedure or a function.
   2) The name of the library routine.
   3) The number and type of parameters the library routine accepts. This is optional.
   4) The programming statements that will process the parameters accepted by the
      library routine.
   5) The value returned if the library routine is a function.
To declare a library routine that is a procedure, here is the syntax required:

   procedure name(parameter declaration, parameter declaration, ...)
      one or more Euphoria statements to execute
   end procedure

To declare a library routine that is a function, here is the syntax required:

   function name(parameter declaration, parameter declaration, ...)
      one or more Euphoria statements to execute
      return value
   end function

The custom functions and procedures you create will of course have a name, just as variables have a name. If your procedure or function accepts values, probably for the purpose of processing them, you need to have parameter declarations as well. Declaring parameters is the same as declaring variables, which makes sense. Parameters are in fact variables that accept the values passed to functions and procedures. The one or more Euphoria statements to execute is the actual muscle of the library routine, which can consist of assignment statements and other library routines, either supplied by Euphoria or created by other programmers. The end of the library routine is always terminated by a end procedure or end function. The return statement is used to end the library routine's execution the moment it is executed. In procedures, it's rare you would need it, unless you plan to end the procedure in midrun. However, in functions, it's needed, because the "return" statement returns a value (either an atom or a sequence) back to the program that called the function.

Here's the simplest example of a custom procedure:

   procedure print_some_lines()
      puts(1, "lines\n")
      puts(1, "lines\n")
      puts(1, "lines\n")
   end procedure

   print_some_lines()

The first five lines define a procedure called print_some_lines(). This procedure accepts no parameters. When executed, it will print three lines of text. The procedure starts running when it is called by name (the print_some_lines() following the declaration portion). Let's build on it a bit more by adding parameters:

   procedure print_some_lines(integer repeat, sequence line_to_print)
      for count = 1 to repeat do
         puts(1, line_to_print & "\n")
      end for
   end procedure

   print_some_lines(10, "Hi There!")

The procedure can now accept two parameters. The first one, "repeat", controls the number of lines to be printed. The second one, "line_to_print", is what is printed "repeat" times. These parameters are declared inside the procedure, as part of the declaration of the procedure itself. Now we have a procedure where its run can be controlled by the parameters it receives. Let's change it to a function to have it return a value back:

   integer status_of_print

   function print_some_lines(integer repeat, sequenc line_to_print)
      if repeat > 50 then
         return 2
      else
         for count = 1 to repeat do
            puts(1, line_to_print & "\n")
         end for
         return 1
      end if
   end function

   status_of_print = print_some_lines(10, "Hi There")

Now the library routine will only print a maximum of 50 lines, returning a value of 1 to let the program that called it know that it printed some lines. Any attempt to print more than 50 lines will cause the library routine to return a value of 2, and print nothing at all. You will notice the way we execute print_some_lines() has changed, by making it a part of an assignment statement, which is how functions in general are run. After print_some_lines() finishes executing, the returned value is stored in variable "status_of_print" A demo program is available to show how to create a function that draws a text square on the screen.

demo program 84
sequence returned_string
function draw_text_rectangle(sequence top_left, sequence bottom_right)
     integer tl_corner,tr_corner,bl_corner,br_corner,across,down

     tl_corner = 'Ú'

     tr_corner = '¿'

     bl_corner = 'À'

     br_corner = 'Ù'

     across = 'Ä'

     down = '³'

     if length(top_left) != 2 then
          return "Invalid Top Left Location"
     elsif length(bottom_right) != 2 then
          return "Invalid Bottom Right Location"
     elsif bottom_right[1]-top_left[1] < 2 then
          return "Rectangle Not High Enough"
     elsif bottom_right[2]-top_left[2] < 2 then
          return "Rectangle Not Wide Enough"
     else
          position(top_left[1],top_left[2])
          puts(1,tl_corner)
          position(top_left[1],bottom_right[2])
          puts(1,tr_corner)
          position(bottom_right[1],top_left[1])
          puts(1,bl_corner)
          position(bottom_right[1],bottom_right[2])
          puts(1,br_corner)
          for lines = top_left[1]+1 to bottom_right[1]-1 do
                position(lines,top_left[2])
                puts(1,down)
                position(lines,bottom_right[2])
                puts(1,down)
          end for
          for columns = top_left[2]+1 to bottom_right[2]-1 do
                position(top_left[1],columns)
                puts(1,across)
                position(bottom_right[1],columns)
                puts(1,across)
          end for
     end if
     return "Text Rectangle Drawn"
end function

clear_screen()
returned_string = draw_text_rectangle({1,1},{18,80})
position(20,1)
puts(1,returned_string & '\n')

You can also design custom variable types in the same manner you design custom library routines. As a matter of fact, declaring a variable type is like designing a one parameter function.

   type name(parameter declaration)
      one or more Euphoria statements to execute
      return true or false value
   end type

Declaring a custom variable type first involves choosing a name for it. Next, a parameter declaration is required to accept the value the custom variable type will hold. one or more Euphoria statements to execute will define what values the custom variable type is allowed to hold. This is done by running a series of tests on the parameter accepted. The outcome of these tests will determine whether a true or false value is returned once the group of code is finished running. At this point, we have two questions to ask: how do we declare a variable of a custom type instead of integer, sequence, atom, or object, and how do we actually execute the "type-end type" group?

If you created a variable type called "housepet", and wanted to declare a variable called "cat" of type "housepet", you would say:

   housepet cat

As to how to execute "type-end type" group, it is started every time we attempt to assign a value to a variable using that custom type:

   1) Euphoria starts the "type-end type" group declaring the custom variable type,
      using the assigned variable as the parameter.
   2) The Euphoria statements executed  within the "type-end type" group tests the
      parameter value, and a value of 1 or 0 is returned.
   3) If the value returned is 1, the assignment statement that triggered the
      "type-end type" sets the variable to the assigned value, otherwise the program
      will end with a type check error message.

Let's put it altogether in this program example:

   type fruit(sequence unknown_fruit)
      sequence valid_fruit
      valid_fruit = {"bananas", "apples", "oranges", "pears"}
      return find(unknown_fruit, valid_fruit)
   end type

   fruit fruit_check
   fruit_check = "pears"
   puts(1, "Program Run Completed\n")

We've created a custom variable type called "fruit", and declared a variable called "fruit_check" of type "fruit". When we attempt to assign a value to "fruit_check" with the value "pears", the "type-end type" group is executed using "pears" as the parameter. As you can see in the "type-end type" the returned value is the result of a find() library routine. If find() cannot find "pears" in the sequence value stored in the variable "valid_fruit", a 0 will be returned. However, we can tell that 1 instead will be returned because "pears" is in the list. As a result, no type check error will occur, and the value "pears" is assigned successfully to "fruit_check" when the "fruit" type group finishes running. The next statement following the assignment of "fruit_check" with "pears" is then executed. Had we used the following assignment statement instead:

   fruit_check = "yams"

then the "type-end type" group would have returned a 0, and the program would immediately halt with the following message:

   type_check failure, fruit_check is {121'y',97'a',109'm',115's'}

Custom variable types can help detect any problems during program design. A programmer can state certain values like time, phone numbers, postal or zip codes, etc, which must follow specified value ranges. If a program process takes a value outside an acceptable range, the "type-end type" group will return a 0 value, causing the program to halt on a type check error. A demo program shows another example of using variable types created by the programmer, this time using helpful diagnostic messages.

demo program 85
include machine.e

type binary(integer values)
     sequence bad_digit
     integer valid_value
     bad_digit     = "**********************************************\n" &
                     "* This type of variable only accepts values  *\n" &
                     "* between 0 and 1! Check EX.ERR for details! *\n" &
                     "**********************************************\n"
     crash_message(bad_digit)
     valid_value = values >= 0 and values <=1
     return valid_value
end type

binary binary_digit

binary_digit = 2
But your learning of custom library routines does not end at this point. There are other questions that need to be answered. For example, are any variables used in one library routine accessible by other library routines, or even the entire program? Do we have to keep everything that makes up a program in a single source file, or can certain parts be stored in several source files? The next chapter will address this when we learn about scope and using include files.

ToC

Program Scope And Include Files

The programs you have been writing so far have used variables that are accessible anywhere within the program. This is convenient, but if you plan to use a variable for a short process, wouldn't it be memory efficient to be able to "undeclare" it? Also, it would be faster to design a program that uses plug-in sections that are included into the program with a single line, instead of retyping or copying source code that already exists elsewhere. Euphoria can handle both problems very easily!

Previously, we briefly touched on scope when we were discussing the "for" statement, where the variable it automatically creates and increments during the loop only exists while the loop repeats. When the "for" statement completes executing, the variable is no longer available for use. This variable, therefore, is said to have "scope".
All Euphoria symbols, such as variables, library routines and type groups, has a scope, or a range limit in the program where it can be referenced. The scope begins the moment it is declared. This means it is impossible to use something before it is declared, so the example listed below is incorrect:

   marbles = marbles + 1
   atom marbles

In addition, the following listed below is also incorrect:

   atom count_3s
   for counter =  1 to 500 do
      count_3s = count_3s + 3
   end for
   printf(1,"%d %d\n",{count_3s, counter})

When run, you should get a message saying that variable "counter" was not declared. In actual fact, it was, but it only stayed around long enough for the "for" statement to complete counting to 500. The moment the "for" statement stopped, the variable "counter" was, in essence, "undeclared". By the time program execution reached the line where printf() is used, the variable "counter" did not exist any more.

Which brings us to an important question: just how far does a variable, library routine, or type group's scope extend?
If a variable is declared automatically by a "for" statement, its scope ends at the "end for" line, and any program statements following can no longer reference it. If a variable is declared inside a function, procedure, or type group, its scope ends at either the "end type", "end procedure", or "end function" line. This means other library routines, type groups, or even the main program can't access it. These kinds of variables are referred to as "private" variables, because only the "for" statement, library routine, or type group the variable was declared inside can reference it. Once any of these processes stop running, the variable is "undeclared", freeing up memory.

If a variable is declared outside a library routine or a type group, then the variable's scope extends from the point of declaration to the end of the program. These kinds of variables can be referenced inside a library routine, a type group, or by programming statements being executed inside a "for" statement, and are called "local" variables. The demo programs you have run up to now use local variables.

It's possible to have a local variable and a private variable with the same name. When executing the library routine or type group it is declared in, the private variable is dominant over the local variable.

Type groups and library routines declared in your program have a scope that starts at the point of declaration to the end of the file they are declared in. They can be referenced by other library routines and type groups, but only if they are declared AFTER. Look at the following example below:

   procedure alpha()
      clear_screen()
   end procedure

   procedure beta()
      alpha()
   end procedure

It is perfectly legal for procedure "beta" to call procedure "alpha" because "alpha" was declared before "beta".

The scope of variables, library routines, and type groups need not be bordered within the confines of a single source file. This means you could reference variables, library routines and type groups that are not even present in your program, but in another source file. This is where the include files comes in.

You can place any variable, library routine, or type group declarations inside include files (.e) that are separate from the program. To use any of these in your program, you reference the include file with the include statement. In the include file where any variables, library routines or type groups are stored, you have to use the word "global" as part of the declaration. global means the scope of the variable, library routine or type group extends indefinitely. It's important to use lobal in any variable, library routine or type group that is declared in an include file, otherwise the scope of each is assumed to be local.

Here are some examples of declarations with the word global:

   global object grab_bag

   global function add_two(atom first, atom second)
      atom sum
      sum = first + second
      return sum
   end function

As a result, include files allow you to design programs that are made up of plug-in code, referenced by the include statement at the top of the program. The file name the include statement references can be be an absolute file and directory path (c:\utilities\icons.e), or a relative file name (graphics.e). If a relative file name is used, Euphoria will first search for it in the same directory the program is currently in. If not found, it will then look in "euphoria\include". The environment variable EUDIR sets the exact path of "euphoria".

Include files can in turn contain include statements referencing other include files, up to 10 levels deep of nesting. Each include statement must be on a single line by itself.

A demo program is available to show how to use include files containing externally defined variables and a procedure.

Demo program 86
include d2808a.e
print(1,find('J',valid_capitals))
puts(1,"\n\n")
show_trademark()


Before running the above demo, copy and paste the following into your editor and name the file 'd2808a.e'. Then put the file in your euphoria\include directory:

global sequence valid_capitals
valid_capitals = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

global procedure show_trademark()
     puts(1,"**********************************************\n")
     puts(1,"*  Copyright 1997 Mom, The Cat Smells Funny! *\n")
     puts(1,"*                Productions                 *\n")
     puts(1,"*                                            *\n")
     puts(1,"*                 |\\\\\"\"\"//|                  *\n")
     puts(1,"*                 {_ O O _}                  *\n")
     puts(1,"*                   {/~\\}                    *\n")
     puts(1,"*                    \"\"\"                     *\n")
     puts(1,"**********************************************\n")
end procedure

The next chapter is the final one in this tutorial, so its purpose is to introduce library routines that were not easily classified into the subjects you have covered. Turn the page now to begin wrapping up your introduction to the Euphoria programming language!

ToC

Wrapping It Up With Mouse And Sound Support

This last chapter of the tutoriaal will teach you how to use other input and output devices besides the keyboard and screen. Most users today prefer the use of a mouse over a keyboard because it does not involve memorizing complex commands. Just move the mouse pointer to a graphics icon and click. Also, nothing adds a little dimension to a game than sound. Sound allows the programmer to get a person's attention by emitting a beep from the speaker, along with a message on the screen. Euphoria programs can use a mouse easily, without any need to understand device driver programming. It is available in all graphics modes. In a pixel graphics mode, the mouse pointer appears as an arrow pointing toward the top left. In text modes, it appears as a solid block. Mouse usage in modes beyond 640 × 480 pixels is possible only in Windows/95+

A mouse point is displayed on the screen using the same co-ordinate system that pixels use, namely a pixel column first, pixel row last pair. Whenever the mouse is used, something called an event is generated. For example, an event can be the mouse being moved across the pad, or one of the mouse buttons being clicked. Your system can only keep track of one event at a time, so previous events are always being replaced with new ones. A Euphoria program can get the last event generated when a mouse is used by using this library routine:

   include mouse.e
   ro = get_mouse()

get_mouse() will return a three-element long sequence that represents the last even generated, to be stored in receiving variable ro. Each element of the sequence is an atom value, the meaning of which is shown below:

   {event, pixel column, pixel row}

The event is an integer value that describes what the mouse did:

   1  - Mouse moved
   2  - Left button pressed
   4  - Left button released
   8  - Right button pressed
   16  - Right button released
   32  - Middle button pressed
   64  - Middle button released

The pixel column and pixel row is where the mouse pointer was displayed when the event occured. It's possible to have an event integer that is the sum of two actions happening simultaneously, such as the right button being held down while the mouse moves across the pad (8 + 1 = 9). The pixel column and pixel row returned could either mean the very tip of the mouse pointer, or the pixel locaton the mouse pointer is pointing at. The only way you can determine this is by testing. If it is the very tip of the mouse pointer being returned, and you want to use get_pixel() to return the colour of the pixel, you have to subtract 1 from both the pixel column and pixel row to get the correct pixel location. If you want to get the actual text column and text row of the mouse pointer, you may have to scale the returned pixel column and pixel row using division.

get_mouse(), when used for the very first time, will turn on your mouse pointer. Your mouse driver must be loaded before you can get mouse events. If no mouse event has been generated since the last use of get_mouse(), a value of -1 is returned. get_mouse() by default returns every event that occurs when your mouse is used. Sometimes this is not practical, as a lot of events can occur in a short period of time, causing you to miss the events that you wan to check for. It would be nice to screen out those mouse events you are not interested in, concentrating on the mouse events that are important.

   include mouse.e
   mouse_events(i)

mouse_events() allows you to select which events (i) get_mouse() should report. i is actually a sum of the specific events you want reported. For example, mouse_events(42) means you want get_mouse() to report only events that involve the left, middle, or right buttons being pressed (2 + 8 + 32). Any other events that occur will be ignored. mouse_events() can be re-issued at any time to change what events you want get_mouse() to report. The very first call to mouse_events() will turn on the mouse pointer for you.

On the subject of mouse pointers, there are times where you need to turn the mouse pointer on or off. For example, you may want to turn off the mouse pointer when you plan to use save_image() to copy an area of the screen where the mouse pointer is currently over. Here's the library routine that can do this for you:

   include mouse.e
   mouse_pointer(i)

i serves as the toggle switch that controls whether your mouse pointer is visible or not. A value of 0 means the mouse pointer is hidden. Any positive value will make the mouse pointer visible again. There's an important note to make regarding the multiple usage of mouse_pointer(). For example, if you issue mouse_pointer(0), say, 3 times, you must issue mouse_pointer(1) the same number of times (3 in this example) to make your mouse pointer appear again. Therefore, it is extremely important for you to keep track of where the mouse pointer is being turned off and on in your program.

Now that we've wrapped up mouse feature, let's wrap up the tutorial by introducing your last library routine to learn.

   include graphics.e
   sound(i)

sound() emits a sound from your speaker. i is the frequency of the sound. The higher the value of i, the higher the pitch generated. A demo sums up all the library routines you have learned here, by using the mouse to emit different sounds by clicking parts of shape!

Demo program 87
include graphics.e
include image.e
include mouse.e
integer current_colour
sequence pattern, bright_magenta_map, bright_blue_map, bright_green_map,
         bright_red_map, bright_white_map, sound_on
object mouse_data
bright_magenta_map = palette(13, {63,63,63})
bright_red_map = palette(12, {63,63,63})
bright_blue_map = palette(9, {63,63,63})
bright_green_map = palette(10, {63,63,63})

bright_white_map = palette(12, bright_red_map)
bright_white_map = palette(9, bright_blue_map)
bright_white_map = palette(10, bright_green_map)
bright_white_map = palette(13, bright_magenta_map)

if graphics_mode(18) then
     puts(1, "Mode Failure")
else
     position(3,10)
     puts(1, "Place the mouse pointer on any of the coloured areas")
     position(4,10)
     puts(1, "of the shape, and click with the left button. A sound")
     position(5,10)
     puts(1, "from your PC speaker will be generated. Click the right")
     position(6,10)
     puts(1, "button to turn off the sound, or press 'q' to quit.")
     pattern = {{290,280},{190,280},{290,180}}
     polygon(9,1,pattern)
     pattern = {{290,180},{290,280},{390,280}}
     polygon(10,1,pattern)
     pattern = {{190,280},{290,280},{290,380}}
     polygon(12,1,pattern)
     pattern = {{290,380},{390,280},{290,280}}
     polygon(13,1,pattern)
     sound_on = "  "
     mouse_events(2+8)
     while get_key() != 'q' do
          mouse_data = get_mouse()
          if sequence(mouse_data) then
               mouse_pointer(0)
               if mouse_data[1] = 2 then
                    current_colour = get_pixel({mouse_data[2],
                                                mouse_data[3]})
                    if current_colour = 13 and
                    compare(sound_on,"  ") = 0 then
                         sound(2100)
                         bright_magenta_map =
                         palette(13, bright_white_map)
                         sound_on = "bm"
                    end if
                    if current_colour = 12 and
                    compare(sound_on,"  ") = 0 then
                         sound(2000)
                         bright_red_map =
                         palette(12, bright_white_map)
                         sound_on = "br"
                    end if
                    if current_colour = 9 and
                    compare(sound_on,"  ") = 0 then
                         sound(1900)
                         bright_blue_map =
                         palette(9, bright_white_map)
                         sound_on = "bb"
                    end if
                    if current_colour = 10 and
                    compare(sound_on,"  ") = 0 then
                         sound(1800)
                         bright_green_map =
                         palette(10, bright_white_map)
                         sound_on = "bg"
                    end if
               end if
               if mouse_data[1] = 8 then
                    sound(0)
                    if compare(sound_on,"bm") = 0 then
                         bright_white_map =
                         palette(13, bright_magenta_map)
                         sound_on = "  "
                    end if
                    if compare(sound_on,"br") = 0 then
                         bright_white_map =
                         palette(12, bright_red_map)
                         sound_on = "  "
                    end if
                    if compare(sound_on,"bb") = 0 then
                         bright_white_map =
                         palette(9, bright_blue_map)
                         sound_on = "  "
                    end if
                    if compare(sound_on,"bg") = 0 then
                         bright_white_map =
                         palette(10, bright_green_map)
                         sound_on = "  "
                    end if
               end if
               mouse_pointer(1)
          end if
     end while
     if graphics_mode(-1) then
          puts(1, "Mode Failure")
     end if
end if
sound(0)


You have reached the end of this tutorial, but not the end of your adventures in Euphoria. The best way to get a strong grasp of this powerful language is by first writing very small programs, then build larger ones. Do not be daunted by any error messages you get. They are easy to understand. However, there is a segment in the appendices at the end of this tutoria on error messages when running EX.EXE, just in case you get a little confused.

Good Luck In Your Euphoria Programming!

Thank You For Using This Tutorial!! :)
ToC