home
Home
documentation
Docs
download
Install
development
Dev
forum
Forum
email
Email

Blue User Manual

Type Reference       Modules       Howto      Examples      OOP

Calling Blue

Call Blue with the following syntax:

   blue scriptname arg1 arg2 arg3 ...

Obviosly if Blue is not in your path, you must explicitly spell out blue's location.

To find an error in a program run Blue with debugging information added:

   blue -g scriptname arg1 arg2 arg3 ...

Hello World

Here is the obligatory "Hello, World" code:

      "Hello, World\n".print();

Language Basics

Assignment

The assignment operator is the equal sign ('='). The assignment itself can be used as a value in another assignment:

    x = y = z = 15;
    sys.print( x = "Hello");

Dynamically Typed

Blue is a dynamically typed language. Variables are not declared as a type. Variables are only names associated with an object. The object has a type, but the variable that refers to the object does not. A variable may refer to a number then later in the program it may refer to a string.

    x = 5;
    x = "Hello";

Expressions Everywhere

Everything is an expression. That is, everything evaluates to a value. For example, 'x=5' assigns the variable 'x' to 5, and also evaluates to 5.

    a = x = 5;

Parenthesis

Parenthesis adjust the order of operation in an expression.

   x + y * z;  # y and z are multiplied then added to x
  (x + y) * z;  # x and y are added together then multiplied by z

Parenthesis can also be used to group expressions together as a single unit. The value of the unit is the value of the final expression in the parenthesized expression. It is similar to the effect of the comma (",") operator in C. The last expression in the parenthesis should not be terminated with a semicolon.

   x = ( a=1; b=2; c=3);  # a, b, and c are set.  then x is set to 3

Attributes

Attributes are other objects linked to the original. All objects can have attributes. Attributes are set and retrieved by using the dot ('.') operator.

   a = 5;                  # create an number object
   a.greet = "Hello\n";    # set an attribute named "greet" for the number object
   sys.print( a.greet );   # access the attribute named "greet"

Some object types have built-in native attributes. For example, Arrays have function attributes like 'length' and 'resize'.

   a = [1,2,3,4];            # create an array of numbers
   sys.print( a.length() );
   b = a.resize(10);
   sys.print( b.length() );

viewing attributes

You can list the attributes of an object using the "sys.attribs" function:

 sys.attribs( object ).print();

You can list the type attributes of an object by adding the typeof operator (see below):

 sys.attribs( &object ).print();

default object

Often code will need to access attributes of the same object repeatedly.

   person1.name   = "Erik";
   person1.gender = "male";
   person1.age    = 36;
   person1.eyes   = "brown;

This can be cumbersome to read and write. Use the 'def' keyword to make the code a little more readable:

   def person1;
    .name   = "Erik";
    .gender = "male";
    .age    = 36;
    .eyes   = "brown;

The def keyword assigns the following object as the default object. When you access an attribute without a variable name it will use the default object.

Although children have not been seen yet, the default object also works with child references:

   def person1;
    .[0]   = "Erik";
    .[1]   = "male";
    .[2]   = 36;
    .[3]   = "brown;

override type attributes

You can override the built-in type attributes on an individual object by simply re-assigning the attribute.

   a = [1,2,3,4];
   b = [1,2,3,4];
   a.resize = func{ raise "Can't resize me!"};
   x = a.resize(10); # this will raise an exception
   y = b.resize(10); # y is the resized b array

typeof operator

You can override, add, and delete attributes to the object's type by using the typeof operator (&).

   a = 5;
   number_type  = &a;
   number_type.greet = "hello";
   sys.print( a.greet );

You can delete attributes by using the del operator.

   a = [1,2,3,4];
   a.greet = "hello";
   del a.greet;

You can use attributes to define on-the-fly structures.

   a = ();           # () is how Blue specifies a NULL object
   a.name = "Erik";
   a.age  = "30";
   a.height = "6ft";

Scope

There are four scopes:

local

Locally scoped variables reside within a function call. They are created during the execution of the function and are destroyed when the function call reaches the end or explicitly returns. Local scope is the default scope. There is no modifier necessary to declare a variable as locally scoped.

   f=func{
       x=1;  # local variable x
       y=2;  # local variable y
   };
   
   f();  # local variables x and y only exist during this call

lexical

Lexical variables exist in the current function and any nested functions. Variables must be declared with the storage modifier "lexical" before the variable name.

   lexical x = 4;

Lexical variables are variables that can be used in the current function and any nested or recursively nested functions.

   f = func{
       lexical x = 4;
       g = func{
           return x + 7;
       };
       h = func{
           i = func{ return x + 2  };
       };
   };
closures

Lexical variables make closures possible in Blue. Closures are functions that maintain the variables that existed when the function was defined. That's a mouthful and a little difficult to wrap your head around the first time you hear it. In order to simplify closures and to avoid carrying around variables that may never get used, only lexical variables are maintained by closures. Here's an example:

   counter = func{
       lexical count = 0;
       next = func{
           count = count + 1;
           return count;
       };
       return next;
   };
   
   x = counter();
   x();   # this will produce 1
   x();   # this will produce 2
   
   y = counter();
   y();   # this will produce 1
   y();   # this will produce 2

global

Global variables are available to any expression in the global variable's module.

   global x;  # anytime the variable x is used it refers to this global variable
   
   f=func{
       x=1;  # global variable x
       y=2;  # local variable y
   };
   
   f();  # local variable y only exist during this call

The global variable declaration can be used during its assignment.

   global x = 1;

sys

The "sys" variable is the only object that is accessible everywhere. It is a warehouse for a number of built-in functions. You may add other objects to it but in larger projects there are probably better solutions.

Types

Types are categories that objects fall into. An object can only be a single type. All objects of a type share functional attributes called methods and/or data attributes called fields. They also define the way that objects interact with one another. You can directly access the type of an object by using the typeof operator (&):

   x = 5;              # create a number
   number_type = &x;   # access the number type

Types and Classes are idealistic little categories that try to define the behavior and state of real world entities. Beautiful object oriented designs can be corrupted by the multitude of seemingly negligible exceptions permeating the real world. Blue tries to alleviate some of the design funk by allowing more adaptive objects.

Runtime type modification

Blue types are not static constructs. Types can be altered during program execution. If you alter the attributes of a type you effectively alter the capabilities of all objects of that type. For example, under normal circumstances a number would not be able to greet you, but in the following demo we give numbers that ability.

  x = 5;
  number_type = &x;
  number_type.greet = func{ sys.print("Hello\n")};
  x.greet();

You can remove type attribute with the del operator.

  x = 5;
  number_type = &x;
  number_type.greet = func{ sys.print("Hello\n")};
  x.greet();
  del number_type.greet;
  x.greet();   #   <-- an exception because the number type no longer supports greet 

Overriding type attributes

When you use the attribute operator (.), the Blue virtual machine searches the current object's attributes. If it can not find the requested attribute, it then searches the object's type attributes.

If you want to alter the behavior for a single object, you don't have to generate an entire new type. Just override the method at the object.

  x = 5;
  x.greet = func{ sys.print("Hi\n")};
  x.greet(); # prints "Hi"

Inline Code Blocks

Inline code blocks allow you to execute code in a contained context. They are similar to the try block in Java. Inline code blocks always return a value. If a return value is not explicitly given it returns a null.

Inline code blocks are part of the current scope. Variables declared in an inline code block do not go out of scope when the inline function exits. The varibles declared in the parent function are accessable to the inline code block.

Inline code blocks can be declared in two ways:

  1. do { ... }
  2. { ... }

   do{
       x=1;
   };
   sys.print( x );

Loop Code Blocks

Loop code blocks are the flow control mechanism that allows looping. Other languages have while, for, do-while, foreach, and other constructs to accomplish looping. Blue is frugal and provides one mechanism.

Loops are identical to inline code blocks, but the will repeated until explicitly returned.

   z = loop{
       x=x+1;
       (x > 5) ? return x;
   };
   sys.print( z );

Conditions

Blue takes an alternate approach to conditions. Most importantly a condition in Blue is an expression not a statement. You can use the condition anywhere a value is expected in your program.

A conditional expression has these three parts:

  1. the condition
  2. the true-expression
  3. the false-expression [optional]

The conditional expression will evaluate to the value of the true-expression or the false-expression, whichever one is activated by the condition. If the condition is false and there is no false-expression, It will evaluate to a null-object.

   condition ? true-expression ;
   condition ? true-expression : false-expression;

condition

The condition is an expression that evaluates to true or false. It is a mandatory part of the conditional construct. In its most basic form Blue treats the number zero (0) as false and any other number as true. Other types define how they evaluate to true or false on a type by type basis. For example, an empty string ("") evaluates to false. All other strings evaluate to true. The condition operator ("?") separates the condition from the true-expression.

Most often in the condition you will see a comparison operator:

You will frequently see logical operators:

Here are some examples of conditions:

    x==1    ?
    x > 2   ?
    x=="Hi" ?
    (x < 4) and (x >-3)  ?

true-expression

The true-expression is mandatory and executes if the condition evaluates to true. If there is a false-expression that follows, the true-expression ends with a colon (":"). If there is no false-expression, the entire conditional will end with a semi-colon (";").

So with these basic rules we can construct a simple condition with the following form ("condition ? true-expression;"). The following conditional will print "Hello" if x is equal to 1:

   x==1  ?  sys.print("Hello");

Use parenthesis to make your code more readable. They will not affect the program's execution speed, but can affect the order of operation.

   (x==1) and (y==2)  ?  sys.print("Hello");

false-expression

The false expression is optional, and it follows the true-expression. It is separated by the true-expression by the else operator (":"); So a full if then else conditional would look like this:

   (x==1)  ?
       sys.print("x is one") :
       sys.print("x in not one");

The false-expression can also be another conditional so a full "if/elseif/else" conditional would look like:

   (x==1)  ?
       sys.print("x is one") :
   (x==2)  ?
       sys.print("x is two") :
    sys.print("x is not one or two");

multi-line conditional expressions

The true and false expressions can only contain one expression. So in order to execute several lines of code there are two choices:

Use an inline function:

   (x==1)  ?  {
       sys.print("x is one");
       y=2;
   };

Use a parenthised expression:

   (x==1)  ?  (
       sys.print("x is one");
       y=2;
   );

Parenthised expressions execute all of their contents but evaluate to the last expression contained in the parenthesis. examples

The following conditional expression evaluates to the value of the expression that it executes. In the following example, if x is equal to 1, then the conditional expression evaluates to 18. In all other cases, it evaluates to 10:

   y = (x==1)  ?  18 : 10;

The previous example looks a little strange. The assignment operator ("=") has a lower precedence than the conditional, so Blue has no problem executing this example. However, to help the human in all of us, you can re-write the code to look like this:

   y  =   ( (x==1) ? 18 : 10 );

Well ... maybe it's still a bit confusing, it just takes some practice.

If there is no false-expression, and the conditional is false, it will evaluate to a null-object.

   x=4;
   y = (x==1)  ?  18;

else-if

An else-if is accomplished by inserting another conditional expression into the false-statement.

The else operator (":") separates the nested conditional expressions. The entire conditional is completed by the semi-colon (";"). The final expression in executes if none of the conditions evaluate to true.

   x == 1 ?  "one"   :
   x == 2 ?  "two"   : 
   x == 3 ?  "three" :
             "big"   ;

Error Handling

Many programming languages are leaning toward the try/catch error handling syntax:

   try{
       Oops!
   }catch SomeTypeOfError{
       Do something usefull
   }

This style works but it forces programmers to indent their main-line logic and emphasise the error handling. It also unwinds the program out of the current line of code.

A less structured style of error handling has functions return values that signify an error. C is a great example of this type of error handling. After every call to a function, you check the return value for a possible error value:

   x = TrySomethingCrazy();
       if (! x) fix x

This is nice becuase you can indent the error handling to emphasise the main-line logic. However this is not what typically happens. Most C programmers use shortcuts that condense the main-line logic and error checking into a single line of code. Another frustration is that there is no standard return value that signifies an error. Some functions return NULL on an error, some return -1, and so on. Without a clear standard approach to this type of implementation it's no wonder that programmers decided to look for something better.

In Blue I tried to take the best of both worlds. You can handle errors easily without taking yourself out of the main-line logic, it is standardized, and uncaught errors propagate up the stack until they are caught or eventually end the program with an error message.

The error trapping operator ('|') catches any critical returns and executes the trap-expression. If the previous value is not critical, the trap expression is ignored.

   func() | trap-expression;

Operators are just shortcuts used to call functions. Consequently, trap can be used to catch errors from operators as well.

   listOfNames[15]  | trap-expression;

Error handling can be done without leaving the current line of code. In the next example if the list listOfNames does not contain a value at index 15 an error is raised, and the trap-expression executes. In this case it evaluates to the string value "John Doe":

   name   =   listOfNames[15]   |   "John Doe";

raise

To generate a critical return use the raise keyword. It works the same as the return keyword, but it returns any object as critical:

   raise raise-expression;
   raise object;

trapped variable

After the trap, the critical object is accessed by the special variable @.

   f = func{
       (args[0] >12)  ?  raise args[0];
   };
   f(13) | sys.print("the number ",@, " is greater than 12 \n" );

In the following example, if the return value of the function is critical, a replacement value of 12 is substituted for the return value:

   x = func() | 12;

For those that like the Java/C++ type error handling, the next example uses an inline function to encapsulate three function calls. If any of the functions generate a critical return, it will be caught.

   do {
       func_1();
       func_2();
       func_3();
   } | {
       sys.print("something went wrong\n");
       "SomeError1" == @ ? 
           sys.print("Error 1 occured \n")  :
       "SomeError2" == @ ? 
           sys.print("Error 2 occured \n")  ;
   };

The following example shows how to use trap to implement default arguments for function calls. If you call the function with no arguments the args variable is empty so requesting either argument 0 or 1 will generate a critical "IndexNotFound" object. The trap catches this critical and evaluates its trap-expression. In this case the expression is a simple number. If only one arguments in supplied args[0] is valid and args[1] is critical. Consequently only the width get the default value of 20. If you supply two arguements both arguments exist and no critical event needs to be caught.

   f = func{
       length = args[0] | 10;
       width  = args[1] | 20;
   };



Copyright © 2004-2009, Erik Lechak

Some content provided by others. See credits