//// Cola Programming Language

The Cola Programming Language

Cola is a modern, statically typed, object-oriented programming language, similar to C#. It is geared towards faster development and more readable code.

I'm a fan of C# and the .NET framework. I also like things about Python, Perl, C++, D and Javascript. I'm a fan of getting stuff done, fast. I'm a fan of using only what I need.

Cola resembles C#. It will compile some C# code. But it isn't strictly compatible. Cola has high level regular expression syntax, simple string comparison operators, standalone functions, omission of function return type (default void), 128-bit decimal type as the default floating point type, auto-allocated arrays, scope-access modifier shorthand, public access by default (for fast prototyping, sorry purists), briefer constructor syntax, and miscellaneous other things that I like. On the TODO list is type pattern matching, C# dynamic, delegates and lambda.

colac can compile real .NET programs right now. However, the compiler is not yet production quality, nor will it be anytime soon. I am learning firsthand to become a better compiler writer.

You can reach me at melvin@cola-lang.org

Download Cola »

					
// Cola supports regular expression as a first class language construct.
// Also, using the "as <identifier&rt" we can declare explicit match variables
// and refer back to them.

   var xml =
    @"<book genre='novel' ISBN='1-861001-57-5' misc='sale item'&rt
         <itle&rtThe Handmaid's Tale</title&rt
         <price&rt14.95</price&rt
      </book&rt";
				
   string words[] = {
      "Handmaid", "Tale", "statically", "initialized", "dynamic", "array", "!"
     };

   // Negative match
   for(var word in words)
      if(word !~ /^(Handmaid|Tale)$/)
         io.println(word);

   // Standard regex
   if( xml =~ /(book)(genre)/ ) {         // implicit match backreference
      io.println("In string: " + $0);
      io.println("we matched: " + $1 + ", " + $2);
   }

   // Escaping the regex delimiter if used within regex

   if( xml =~ /(.*?)<\/price>/ )
      io.println("Found price : " + $1); 


   // Use alternate delimiters ## to avoid escaping common forward slash with XML/HTML

   if( xml =~ #(.*?)# )
      io.println("Found price : " + $1); 


   // We can explicitly name our regex matches like declaring a variable
   // in order to use $0, R1, etc. in a nested fashion

   if( xml =~ /()/ as book ) {
      io.println(book.$1);


      // Unqualified $1 refers to latest match at any given time, when a new match
      // is executed, the new $1 is in scope.
      // To avoid scope issues, we can refer to specific matches (book.$1, genre.$1).

      if( $1 =~ /genre='(.*?)'/ as genre ) {
         io.println("outer match: " + book.$1);
         io.println("inner match: " + genre.$1);
         io.println("inner match: " + $1);
      }
   }

   
				

Use the .NET Framework

Cola currently targets the .NET CLR runtime, so it has access to all of the .NET framework, from Winforms, ADO.NET, etc. The support is still basic, but is coming along. 0.24 adds the "using" directive to import namespaces into the current namespace. On the TODO list are events, delegates and lambdas as well as custom attributes. Cola.NET requires the .NET framework or Mono.


	
// .NET System.Xml Example
using System.Xml;

main() {
   var doc = new XmlDocument();
   doc.LoadXml(
       @"<book genre='novel' ISBN='1-861001-57-5' misc='sale item'&rt
         <itle&rtThe Handmaid's Tale</title&rt
                <price&rt14.95</price&rt
                </book&rt" );

   var element = doc.DocumentElement;
   io.println(element.InnerXml);
}


using System.Windows.Forms;

main() {
   var form = new Form();
   form.Text = "Cola Winforms Sample";
   MessageBox.Show("Testing", "Testing", MessageBoxButtons.YesNoCancel);
   
   Application.Run(form);
}



// ADO.NET Oracle sample
//
//   colac ado.cola -r Oracle.DataAccess
//
main() {
   string ename, job;
   var conn = new Oracle.DataAccess.Client.OracleConnection(
                        "Data Source=DEV;User Id=scott;Password=tiger;");
   conn.Open();
   var cmd = new Oracle.DataAccess.Client.OracleCommand("SELECT ENAME, JOB FROM EMP", conn);
   var reader = cmd.ExecuteReader();
   while(reader.Read()) {
      ename = reader.GetString(0);
      job = reader.GetString(1);
      // All names that start with S
      if(ename =~ /^S/)
         io.println("Employee: " + ename + ", Job: " + job);
   }
   conn.Close();
}

Static Typing Object Oriented

The usual Object Oriented features such as inheritance, encapsulation, polymorphism, aggregation, constructors. Pretty much any OO aspect of C# is included in Cola. Cola doesn't deviate from the successful C# / CTS design, and you can easily round trip code from Cola to MSIL to C#. Some immediate differences you may notice is the lack of per-member scope access control. Cola provides two alternate syntaxes for that.

	
// tetris.cola
//
// Full featured Tetris game with scoring
//
using System;


class GameBoard
{
     //Point grid[20 + 1][10 + 2]; // TODO - constant expressions
     Point grid[22][14]; // extra room in array for move checking without worrying about array bounds
     int score;
     int level;
     int rowsCompleted;

     new() {
          score = 0;
          level = 1;
          // Initialize the board / grid with a border of fixed blocks
          for(int x = 0; x < 14; x++) {
               var border = new Point(x, 20, PointType.Permanent);
               border.Draw(this);
               border = new Point(x, 21, PointType.Invis);
               border.Draw(this);
          }

          for(int y = 0; y < 21; y++) {
               var border = new Point(0, y, PointType.Invis);
               border.Draw(this);
               border = new Point(1, y, PointType.Permanent);
               border.Draw(this);
               border = new Point(12, y, PointType.Permanent);
               border.Draw(this);
               border = new Point(13, y, PointType.Invis);
               border.Draw(this);
          }

          AddScore(0); // draw score
     }

     int CheckRows()
     {
          int count = 0;
          bool gap = false;
          for(int row = 1; row < 20; row++) {
               gap = false;
               // Check this row
               for(int col = 1; col < 11; col++) {
                    if(grid[col][row] == null)
                         gap = true;
               }

               // If no gap, row is solid
               if(!gap) {
                    ++count;
                    ++rowsCompleted;
                    level = rowsCompleted / 10 + 1;
                    DeleteRow(row);
                    AddScore(100*level);
                    if(row > 1) {
                         for(int rowAbove = row-1; rowAbove >= 1; rowAbove--)
                              DropRow(rowAbove);
                    }
               }
          }
          return count;
     }

     bool IsCoordOpen(int x, int y)
     {
          if(grid[x][y] == null)
               return true;
          return false;
     }

     void DeleteRow(int row)
     {
          for(int col = 2; col < 12; col++) {
               if(grid[col][row] != null)
                    grid[col][row].Erase(this);
               grid[col][row] = null;
          }
     }

     // Move the row one down
     void DropRow(int row)
     {
          for(int col = 2; col < 12; col++) {
               if(grid[col][row] != null)
                    grid[col][row].Move(this, 0, 1);
          }
     }

     void AddScore(int i) 
     {
          score += i;
          console.gotoxy(20,2);
          io.print("Score: " + score + "   ");
          console.gotoxy(20,4);
          io.print("Level: " + level + "   ");
          console.gotoxy(20,5);
          io.print("Rows:  " + rowsCompleted + "   ");
     }
}

// Different ways to draw a block
enum PointType
{
     Permanent, // border
     Shape,  // live piece
     Invis,
     Dead // old pieces
}

// A single coordinate / block
class Point
{
     int x, y;
     PointType type;

     new(x, y, type) {}

     void Draw(GameBoard board) {
          console.gotoxy(x,y);
          if(type == PointType.Permanent)
               io.print('H');
          else if(type == PointType.Invis)
               io.print(' ');
          else if(type == PointType.Shape)
               io.print('O');
          else
               io.print('0');
          board.grid[x][y] = this; // add to grid
     }

     void Erase(GameBoard board) {
          console.gotoxy(x,y);
          io.print(' ');
          board.grid[x][y] = null; // remove from grid
     }

     bool CanMove(GameBoard board, int xmove, int ymove) {
          // Is the point occupied or not?
          if(board.grid[x + xmove][y + ymove] == null 
               || board.grid[x + xmove][y + ymove].type == PointType.Shape)
               return true;
          return false;
     }

     void Move(GameBoard board, int xmove, int ymove) {
          Erase(board);
          x += xmove;
          y += ymove;
          Draw(board);
     }

     void Die(GameBoard board) {
          board.grid[x][y] = this; // ensure in grid
          type = PointType.Dead; // change to permanent
          Draw(board);
     }
}

class Shape
{
      GameBoard board;
      Point center;
      Point blocks[5];
      int numBlocks;

      new() {
            numBlocks = 0;
      }

      new(GameBoard board, int x, int y) {
            numBlocks = 1;
            center = new Point(x, y, PointType.Shape);
            this.board = board;
      }

      void Draw() {
            for(int i = 0; i < numBlocks; i++)
                  blocks[i].Draw(board);
      }

      void Die() {
            for(int i = 0; i < numBlocks; i++)
                  blocks[i].Die(board);
      }

      void Erase() {
            for(int i = 0; i < numBlocks; i++)
                  blocks[i].Erase(board);
      }

      bool CanMove(int xmove, int ymove) {
            for(int i = 0; i < numBlocks; i++)
                  if(blocks[i].CanMove(board, xmove, ymove) != true)
                        return false;
            return true;
      }

      // Move shape by x/y offset
      virtual void Move(int x, int y) {
            //io.println("numBlocks: " + numBlocks);
            Erase();
            for(int i = 0; i < numBlocks; i++) {
                  blocks[i].Move(board, x, y);
            }
            Draw();
      }

     // Standard rotate around a center
     virtual void Rotate() 
     {
          int i;
          int oldX;
          Erase();

          for(i = 1; i < numBlocks; i++) {
               int tryX = center.x + center.y - blocks[i].y;
               int tryY = center.y + blocks[i].x - center.x;
               if(board.IsCoordOpen(tryX, tryY) == false) { // bug with if(!expr) so using reverse logic
                    Draw();
                    return;  
               }
          }
          for(i = 1; i < numBlocks; i++) {
               oldX = blocks[i].x;
               blocks[i].x = center.x + center.y - blocks[i].y;
               blocks[i].y = center.y + oldX - center.x;
          }

          Draw();
     }

}

class Tee : Shape
{
      new(GameBoard board, int x, int y)
            : base(board, x, y)
      {
            //center = new Point(x, y);
            numBlocks = 4;
            blocks[0] = center;
            blocks[1] = new Point(x + 1, y, PointType.Shape);
            blocks[2] = new Point(x - 1, y, PointType.Shape);
            blocks[3] = new Point(x, y + 1, PointType.Shape);
      }
}

class ShortLine : Shape
{
      new(GameBoard board, int x, int y)
            : base(board, x, y)
      {
            //center = new Point(x, y);
            numBlocks = 3;
            blocks[0] = center;
            blocks[1] = new Point(x + 1, y, PointType.Shape);
            blocks[2] = new Point(x - 1, y, PointType.Shape);
      }
}

class LongLine : Shape
{
      new(GameBoard board, int x, int y)
            : base(board, x, y)
      {
            //center = new Point(x, y);
            numBlocks = 4;
            blocks[0] = center;
            blocks[1] = new Point(x + 1, y, PointType.Shape);
            blocks[2] = new Point(x - 1, y, PointType.Shape);
            blocks[3] = new Point(x - 2, y, PointType.Shape);
      }
}

class LeftAngle : Shape
{
      new(GameBoard board, int x, int y)
            : base(board, x, y)
      {
            //center = new Point(x, y);
            numBlocks = 4;
            blocks[0] = center;
            blocks[1] = new Point(x + 1, y, PointType.Shape);
            blocks[2] = new Point(x - 1, y, PointType.Shape);
            blocks[3] = new Point(x - 1, y - 1, PointType.Shape);
      }
}

class RightAngle : Shape
{
      new(GameBoard board, int x, int y)
            : base(board, x, y)
      {
            //center = new Point(x, y);
            numBlocks = 4;
            blocks[0] = center;
            blocks[1] = new Point(x + 1, y, PointType.Shape);
            blocks[2] = new Point(x - 1, y, PointType.Shape);
            blocks[3] = new Point(x + 1, y - 1, PointType.Shape);
      }
}

class Square : Shape
{
   int position;

      new(GameBoard board, int x, int y)
            : base(board, x, y)
      {
            position = 0;
            numBlocks = 4;
            blocks[0] = center;
            blocks[1] = new Point(x + 1, y, PointType.Shape);
            blocks[2] = new Point(x, y + 1, PointType.Shape);
            blocks[3] = new Point(x + 1, y + 1, PointType.Shape);
      }

      virtual void Rotate()
      {
          Erase();
          if(position == 0 || position == 3) {
               if((blocks[0].CanMove(board, 1, 0)) && (blocks[1].CanMove(board, 1, 0)))
               {
                    blocks[0].Move(board, 1, 0);
                    blocks[1].Move(board, 1, 0);
                    position = (position + 1) % 4;
               }
          } else if(position == 1 || position == 2) {
               if((blocks[0].CanMove(board, -1, 0)) &&(blocks[1].CanMove(board, -1, 0)))
               {
                    blocks[0].Move(board, -1, 0);
                    blocks[1].Move(board, -1, 0);
                    position = (position + 1) % 4;
               }
          }
          Draw();
      }
}


main()
{
     Random random = new Random();
     console.clrscr();
     console.cursor_off();

     GameBoard board = new GameBoard();

     int ch;
     int xStart = 5,
          yStart = 3;
     int PULSE = 1000;
     int sleepTime = 0;

     Shape shape = new Tee(board, xStart, yStart);
     shape.Draw();

     while(1) 
     {
          if(io.peekch()) {
               ch = io.getch();
               if(ch == 38)                                 // UpArrow
                    shape.Rotate();
               else if(ch == 37 && shape.CanMove(-1, 0))	// LeftArrow
                    shape.Move(-1, 0);
               else if(ch == 39 && shape.CanMove(1, 0))	// RightArrow
                    shape.Move(1, 0);
               else if(ch == 40 && shape.CanMove(0, 1))	// DownArrow
                    shape.Move(0, 1);
          }
          os.sleep(25);
          sleepTime += 25;
          if(sleepTime > PULSE) {
               if(shape.CanMove(0,1))
                    shape.Move(0, 1);
               else {
                    // at bottom, old shape becomes part of the landscape :)
                    shape.Die();
                    int oldLevel = board.level;
                    int rowsCompleted = board.CheckRows();
                    if(board.level > oldLevel)
                         PULSE = PULSE * 0.85;
                    // make a new shape
                    switch(random.Next(0,5)) {
                         case 0:
                                   shape = new Tee(board, xStart, yStart);
                         case 1:
                                   shape = new ShortLine(board, xStart, yStart);
                         case 2:
                                   shape = new Square(board, xStart, yStart);
                         case 3:
                                   shape = new LongLine(board, xStart, yStart);
                         case 4:
                                   shape = new LeftAngle(board, xStart, yStart);
                         default:
                                   shape = new RightAngle(board, xStart, yStart);
                    }
               }
               sleepTime = 0;
          }
     }
}




Generics are In for 0.20

I am happy to announce Cola 0.20 with generics! I use a CTS compatible naming convention like Microsoft C# and VB.NET compilers. I have not yet begun testing nested generics but wrote the code with that in mind. I am very proud of this feature, it is something I have been planning for several years. Thanks to my wife for putting up with me lately as I crank out code on my compiler. :)

	
//
// First working sample of generics / containers
// Also demonstrates default(T), nested array of T and generic constructor

   class Stack<T>
   {
      // fields
      int   @top;
      T     @vector[100];
 
      // properties
      int   Length { get { return top; } set { top = value; } }

      T Top
      {
         get { if (top > 0) return vector[top - 1]; return default(T); }
      }

      T Bottom
      { 
         get { if (top > 0) return vector[0]; return default(T); }
      }

      // constructor
      new() {
         top = 0;
      }

      // methods
      void Push(T item)
      { 
         vector[top++] = item;
      }

      T Pop()
      {
         T t = Front();
         if(top > 0)
            top--;
         return t;
      }

      T Front()
      {
         if(top > 0)
            return vector[top -1 ];

         return default(T);
      }
   }

   main()
   {
      var stack = new Stack();

      stack.Push("A");
      stack.Push("B");
      stack.Push("C");

      io.println("Stack has " + stack.Length + " items");
      io.println("Top item is " + stack.Top);
      io.println("Bottom item is " + stack.Bottom);

      io.println(stack.Pop());
      io.println(stack.Pop());
      io.println(stack.Pop());

      io.println("Stack has " + stack.Length + " items");
   }

Array/List Syntax

Cola supports N-dimensional dynamic and auto arrays with dual declaration syntax options. You can use C/C++ style postfix notation (int arr[10]) or C#/Java-like prefix notation (int[10] arr). When Cola sees a postfix declaration it automatically allocates storage for the array. Pre-fix "Java" arrays work like Java arrays. You can use curly braces { } for inline array literals in loops or arguments, or as static initializers.

	
main()
{
   int small[] = {1, 2, 3};

   string words[] = {
      "This", "is", "a", "statically", "initialized", "dynamic", "array", "!"
    };

   int big[] = {
      1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
      11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
      21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
      31, 32, 33, 34, 35, 36, 37, 38, 39, 30,
      41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
      51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
      61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
      71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
      81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
      91, 92, 93, 94, 95, 96, 97, 98, 99, 100
   };

   for(int j = 0; j < big.Length; j++)
      io.println(big[j]);

   for(int word = 0; word < words.Length; word++)
      io.print(words[word] + " ");
   io.println("");

   for(int i = 0; i < small.Length; i++)
      io.println(small[i]);

   for(var item in {1,2,3,4,5,5,4,3,2,1}) {
      io.println(item);
   }

   for(var s in {"this","is", "a", "inline", "array", "!"}) {
      io.println(s);
   }
   
   // N-Dimensional Auto-allocated Arrays 
   
   string board[3][3];  // auto
	
   board[0][0] = "X";
   board[0][1] = "O";
   board[0][2] = "X";
   board[1][0] = "O";
   board[1][1] = "X";
   board[1][2] = "O";
   board[2][0] = "X";
   board[2][1] = "O";
   board[2][2] = "X";

   io.println("Tic Tac Toe: ");

   for(int x = 0; x < 3; x++) {
      for(int y = 0; y < 3; y++)
         io.print(board[x][y]);
      io.println("");
   }
}

.NET Framework Metadata Update for 0.23 - Generics and Properties Added

0.23 gives some attention to the .NET framework integration. colastub.exe now generates C# style properties and generics now that Cola supports them. The core framework prototype files were also updated. If you have a different version of the framework you can move the files aside and compile a program to generate new proto files. Be advised, colastub isn't perfect; you may have to edit files by hand to fix. This is work in progress. Any takers? :)

	
// .NET System.Diagnostics.Process.Start sample to execute an OS command
main() {
   var s = system("colac.exe", "tetris.cola");
   io.println(s);
}

// Cola provides this as os.system()
string system(string cmd, string args)
{
   var p = new System.Diagnostics.Process();
   
   p.StartInfo.FileName = cmd;
   p.StartInfo.Arguments = args;
   p.StartInfo.UseShellExecute = false;
   p.StartInfo.RedirectStandardOutput = true;   
   p.StartInfo.RedirectStandardError = true;   
   p.Start();
   string ret = p.StandardError.ReadToEnd();  // read child process output
   p.WaitForExit();
   return ret;
}

Constructor Delegation

Simply name your constructors new(). I may add traditional C#/C++/Java syntax for compatibility / habit, but I prefer to use new() for clarity and brevity. Call base class with base() or delegate to another constructor with this(). If you name the parameter after a field and leave off the type, Cola will bind it by name and initialize the field immediately. This allows you to skip writing assignment statements and encourages RAII practices.

		  
class A
{
   int x, y;

   new()
   {
      io.println( "A::new()");
   }

   new(x, y)
      // : this(1,2,3) uncomment this to test dependency loops once compiler detects them
   {
	   // this.x and this.y are initialized by name
      io.println( "A::new(x,y): x: " + x + " y: " + y);
   }

   new(int argx, int argy, int argz)
      : this(argx,argy)
   {
      io.println( "A::new(int argx, int argy, int argz): argz: " + argx + " argy: " + argy + " argz: " + argz);
   }
}

class B : A
{
   new() : base(1,2,3)
   {
      io.println( "B::new()");
   }
};
	

C++ Style Scope Access = Less Clutter

The public keyword isn't supported as part of an individual declaration per Java/C#, but I have added C++ style blocks. I have always felt that this was the area in these languages where we were forced to repeat ourselves ceremoniously over and over, and it obscures the fields and methods to the point where our brains "hide" those keywords from us. You can instead use @ for private fields if you want per-field scope access. I am still experimenting with alternatives and I am open to ideas.

	
class Foo
{
private:
   int x;

public:
   new(x)
   {
      io.println( "constructor: this.x initialized to: " + this.x);
   }
}

0.28 Includes Member Initialization Syntax

I am supporting both C# and Javascript syntax for object and container construction / initialization, so the equal sign and the colon are interchangeable within the member initializers. Due to some trouble with the parser, I had to leave out the empty parentheses as an option when using member initializers, so it is "new Foo { ... }" but not "new Foo() { .. }" until I debug the grammar. Parens are only allowed when using a standard constructor. This is the suggested syntax for writing the same thing in C# anyway, but C# does handle the optional parens.

	
// Member initialization syntax

class Person
{
   string  Name;
   int     Age;
   int     Items[];
   string  Description;
}

main()
{   
   var p = new Person {
      Items: {1,2,3,4,5},
      Name: "Melvin",
      Age:  41,
      Description = "This is a description...."   // equals sign also works
   };

   io.println(p.Name);
   io.println(p.Age);
   io.println(p.Description);
   foreach(var item in p.Items) {
      io.println(item);
   }
}


Getting Involved

Cola is still in prototype stage. I am building it to experiment with different constructs and features, and to finalize the grammar. You can currently mix Cola into a larger .NET project. Check the examples directory for the current supported features.

If you are interested in Cola, please download the compiler and look at the examples. If you aren't discouraged by a buggy, command line compiler, and would like to help me out, drop me an email. If you don't have compiler building skills that's ok, there are tasks like implementing a test harness or writing test cases or standard library modules.

I specifically need someone to help write standard library modules in pure Cola. It can start in C#, but eventually it needs to be translated to Cola because I plan to create a LLVM backend to produce native binaries.

The original compiler is written in C++ with Bison; at the time I began, in 2002, I thought LALR was more desirable, but I've since changed my mind. Eventually I plan to reimplement the parser by hand.