Pascal Whirlwind

From Compsci.ca Wiki

(Difference between revisions)
Jump to: navigation, search

Revision as of 03:54, 14 October 2008

Contents

Hello, Pascal!

Let me just open my text editor and create a file called hellopascal.pas.

program HelloPascal;
begin
   writeln('Hello, Pascal!')
end.

And then assuming I've installed the Free Pascal Compiler, I simply opern my command-line, run "fpc hellopascal", and I'm left with a binary by the same name. I don't need to attach the ".pas" extension. The compiler knows to look for source files with that extension.

Names for things

Let's give that message a name.

program HelloPascal;
const
   greeting = 'Hello, Pascal!';
begin
   writeln(greeting)
end.

It's a tribute to Pascal that what I've done is pretty easy to see. I've started a const clause, in which I define constant values and give them names.

But why the semi-colon there? Pascal uses semi-colons as separators. The semi-colon indicates to the compiler that I wish to separate two things. In thise case, I'm separating the definition of the constant greeting, and the begin keyword. One is not necessary after the call to the writeln procedure because Pascal gives special importance to the "end" keyword.

Let's say that I now wish to give a name to the very action of writing that message. I'll create a procedure, sicne procedures and fucntions are how executable code is primarily grouped and named.

program HelloPascal;
const
   greeting = 'Hello, Pascal!';
   
   procedure WriteMessage;
   begin
      writeln(greeting)
   end;
begin
   WriteMessage
end.

Greeting anyone

A bit of flexibility would be nice. To that end I'll define a greet procedure with a name parameter, and then pass 'world' as its argument when I call it.

program HelloPascal;
const
   greeting = 'Hello, Pascal!';

   procedure WriteMessage;
   begin
      writeln(greeting)
   end;

   procedure Greet(name: string);
   begin
      writeln('Hello, ' + name + '!')
   end;
begin
   WriteMessage;
   Greet('world')
end.

Let's discriminate

program HelloPascal;
   procedure Greet(name: string);
   begin
      if name = 'wtd' then
         writeln(name + '?!  That cur!')
      else
         writeln('Hello, ' + name + '!')
   end;
begin
   Greet('wtd')
end.

But that only said one thing to wtd, and that's just not enough.

program HelloPascal;
   procedure Greet(name: string);
   begin
      if name = 'wtd' then 
         begin
            writeln(name + '?!  That cur!');
            writeln('Off with the scoundrel''s head!')
         end
      else
         writeln('Hello, ' + name + '!')
   end;
begin
   Greet('wtd')
end.

A quick note

Pascal is case-insensitive, so you may see code with If or IF. That's fine. You can define Greet and then use it as greet, and that is also fine. The best advice is to pick a style and stick to it.

Let's use a type

Let's create a record type which contains a more complex name, and a procedure which acts on it.

program HelloPascal;
type
   name = record
      first, last : string;
   end;

   procedure Greet(n : name);
   begin
      writeln('Hello, ', n.first, ' ', n.last, '!')
   end;

var
   bobsName : name;
begin
   bobsName.first := 'Bob';
   bobsName.last := 'Smith';

   Greet(bobsName)
end.

Let's add a bit of sugar

program HelloPascal;
type
   name = record
      first, last : string;
   end;

   procedure Greet(n : name);
   begin
      with n do
         writeln('Hello, ', first, ' ', last, '!')
   end;

var
   bobsName : name;
begin
   bobsName.first := 'Bob';
   bobsName.last := 'Smith';

   Greet(bobsName)
end.

Isn't consistency grand?

program HelloPascal;
type
   name = record
      first, last : string;
   end;

   procedure Greet(n : name);
   begin
      with n do
         writeln('Hello, ', first, ' ', last, '!')
   end;

var
   bobsName : name;
begin
   with bobsName do begin
      first := 'Bob';
      last := 'Smith';
   end;

   Greet(bobsName)
end.

Note how I provided a block of code to the with construct, rather than just a single statement. You should also note that this is how I did the same for conditionals.

New names are good

So let's create a procedure which helps us create them.

program HelloPascal;
type
   name = record
      first, last : string;
   end;
   
   procedure InitializeName(var n : name; first, last : string);
   begin
      n.first := first;
      n.last := last
   end;

   procedure Greet(n : name);
   begin
      with n do
         writeln('Hello, ', first, ' ', last, '!')
   end;

var
   bobsName : name;
begin
   InitializeName(bobsName, 'Bob', 'Smith');

   Greet(bobsName)
end.

There's only one problem...

In order to initialize a name, I have to have a name variable. I can't just create a name and use it directly. For that I'd need a function.

program HelloPascal;
type
   name = record
      first, last : string;
   end;
   
   function NewName(first, last : string) : name;
   var
      n : name;
   begin
      n.first := first;
      n.last := last;
      
      NewName := n
   end;

   procedure Greet(n : name);
   begin
      with n do
         writeln('Hello, ', first, ' ', last, '!')
   end;

var
   bobsName : name;
begin
   bobsName := NewName('Bob', 'Smith');

   Greet(bobsName)
end.

Or just:

program HelloPascal;
type
   name = record
      first, last : string;
   end;
   
   function NewName(first, last : string) : name;
   var
      n : name;
   begin
      n.first := first;
      n.last := last;
      
      NewName := n
   end;

   procedure Greet(n : name);
   begin
      with n do
         writeln('Hello, ', first, ' ', last, '!')
   end;
begin
   Greet(NewName('Bob', 'Smith'))
end.

Time for some cleaning up

We've got a decent-sized program now, and that's good. But what if I want to use my name type and it's associated routines elsewhere?

Well, let's create a file named nameunit.pas that will contain a unit, or module of code.

unit NameUnit;

interface

   type
       name = record
          first, last : string;
      end;

   function NewName(first, last : string) : name;
   procedure Greet(n : name);

implementation

   function NewName(first, last : string) : name;
   var
      n : name;
   begin
      n.first := first;
      n.last := last;

      NewName := n
   end;

   procedure Greet(n : name);
   begin
      with n do
         writeln('Hello, ', first, ' ', last, '!')
   end;

end.

And now we'll rewrite our hellopascal.pas program.

program HelloPascal;
uses
   NameUnit;
begin
   Greet(NewName('Bob', 'Smith'))
end.

Much nicer, eh?

Whereas previous programs had been compiled with "fpc hellopascal", this new setup shall be compiled with "fpc hellopascal nameunit".

A Pointless Array

program HelloPascal;
uses
   NameUnit;
var
   names : array [1..10] of Name;
begin
   names[1] := NewName('Bob', 'Smith');
   Greet(names[1])
end.

Slightly less pointless

program HelloPascal;
uses
   NameUnit;
var
   names : array [1..10] of Name;
   i : Integer;
begin
   for i := 1 to 10 do
      names'' := NewName('Bob', 'Smith');

   for i := 1 to 10 do
      Greet(names[1])
end.

Or perhaps:

program HelloPascal;
uses
   NameUnit;
var
   names : array [1..10] of Name;
   i : Integer;
begin
   for i := 1 to 10 do
   begin
      names''.first := 'Bob';
      names''.last := 'Smith';
   end;

   for i := 1 to 10 do
      Greet(names[1])
end.

Or even:

program HelloPascal;
uses
   NameUnit;
var
   names : array [1..10] of Name;
   i : Integer;
begin
   for i := 1 to 10 do
   with names'' do
   begin
      first := 'Bob';
      last := 'Smith';
   end;

   for i := 1 to 10 do
      Greet(names[1])
end.

Pointers Galore!

Arrays are so static. Let's use a linked list instead. That means pointers.

program HelloPascal;
uses
   NameUnit;
type
   PNode = ^TNode;

   TNode = record
      Data : Name;
      Next : PNode;
   end;
var
   list : PNode;

   procedure GreetAllInList(list : PNode);
   var
      current : PNode;
   begin
      current := list;

      while current <> nil do
      with current^ do
      begin
         Greet(data);

         current := next;
      end;
   end;

   procedure NewList(var list : PNode; value : Name);
   begin
      New(list);

      with list^ do
      begin
         data := value;
         next := nil;
      end;
   end;

   procedure AppendToList(var list : PNode; value : Name);
   var
      current, last : PNode;
   begin
      if list = nil then
         NewList(list, value)
      else begin
         current := list;

         while current <> nil do
         with current^ do
         begin
            last := current;
            current := next;
         end;

         NewList(last^.next, value);
      end;
   end;
begin
   AppendToList(list, NewName('Foo', 'Bar'));
   AppendToList(list, NewName('Bob', 'Smith'));

   GreetAllInList(list);
end.

Aren't objects grand?

Wha?! Pascal has objects? Well, yes, Pascal has the ability to work with objects. In the following I'll be using the Free Pascal compiler's OO extension. As a result, I have to compile with the "-S2" option.

program Test;
uses
   Name, NameList;
var
   list : TNameList;
begin
   list := TNameList.Create;

   list.Append(TName.Create('Bob', 'Smith'));
   list.Append(TName.Create('Foo', 'Bar'));

   list.GreetAll;
end.

And to make that possible, the following two units.

unit Name;

interface

   type
      TName = class
	     First, Last : String;
      public
	     constructor Create(f, l : String);

	     function FullName : String;
	     procedure Greet;
      end;

implementation

   constructor TName.Create(f, l : String);
   begin
      First := f;
      Last := l;
   end;

   function TName.FullName : String;
   begin
      FullName := First + ' ' + Last;
   end;

   procedure TName.Greet;
   begin
      writeln('Hello, ', FullName, '!');
   end;

end.
unit NameList;

interface

   uses

      Name;

   type
      PNode = ^TNode;

      TNode = record
         Data : TName;
         Next : PNode;
      end;

      TNameList = class
         Head : PNode;
      public
         constructor Create;

         procedure Append(NewName : TName);
         procedure GreetAll;
      end;

implementation

   constructor TNameList.Create;
   begin
      Head := nil;
   end;

   procedure TNameList.Append(NewName : TName);
   var
      current : PNode;
   begin
      if Head = nil then
         begin
            New(Head);
            Head^.Data := NewName;
            Head^.Next := nil;
         end
      else
         begin
            current := Head;

            while current^.Next <> nil do
               current := current^.Next;

            New(current^.Next);
            with current^.Next^ do
            begin
               Data := NewName;
               Next := nil;
            end;
         end;
   end;

   procedure TNameList.GreetAll;
   var
      current : PNode;
   begin
      current := Head;

      while current <> nil do
      begin
         current^.Data.Greet;
         current := current^.Next;
      end;
   end;

end.

Objects and interfaces

So we've seen that it's possible to have objects in Pascal. But then, some would argue that interfaces are a key concept in object-oriented programming. Pascal can handle that too.

program InterfaceTest;

type
   IHasName = interface
      function Name : string;
   end;

   TName = class(TInterfacedObject, IHasName)
      constructor Create(f, l : string);

      function First : string;
      function Last : string;
      function Name : string;
   private
      FFirst, FLast : string;
   end;

   TSingleName = class(TInterfacedObject, IHasName)
      constructor Create(n : string);

      function Name : string;
   private
      FName : string;
   end;

   { The Greet function }

   procedure Greet(n : IHasName);
   begin
      writeln('Hello, ', n.Name, '!');
   end;

   { Implement TName }

   constructor TName.Create(f, l : string);
   begin
      FFirst := f;
      FLast := l;
   end;

   function TName.First : string;
   begin
      First := FFirst;
   end;

   function TName.Last : string;
   begin
      Last := FLast;
   end;

   function TName.Name : string;
   begin
      Name := FFirst + ' ' + FLast;
   end;

   { Implement TSingleName }

   constructor TSingleName.Create(n : string);
   begin
      FName := n;
   end;

   function TSingleName.Name : string;
   begin
      Name := FName;
   end;

var
   Names : array [1..2] of IHasName;
   Index : integer;
   
begin
   Names[1] := TName.Create('Bob', 'Smith');
   Names[2] := TSingleName.Create('Foo');

   for Index := 1 to 2 do Greet(Names[Index]);
end.

Abstract Base Classes

Of course, we could accomplish much the same with an abstract class.

program AbstractClassTest;

type
   TNameBase = class(TObject)
      function Name : string; virtual; abstract;
      procedure Greet;
   end;

   TName = class(TNameBase)
      constructor Create(f, l : string);

      function First : string;
      function Last : string;
      function Name : string; override;
   private
      FFirst, FLast : string;
   end;

   TSingleName = class(TNameBase)
      constructor Create(n : string);

      function Name : string; override;
   private
      FName : string;
   end;

   { Implement TNameBase }

   procedure TNameBase.Greet;
   begin
      writeln('Hello, ', Name, '!');
   end;

   { Implement TName }

   constructor TName.Create(f, l : string);
   begin
      FFirst := f;
      FLast := l;
   end;

   function TName.First : string;
   begin
      First := FFirst;
   end;

   function TName.Last : string;
   begin
      Last := FLast;
   end;

   function TName.Name : string;
   begin
      Name := FFirst + ' ' + FLast;
   end;

   { Implement TSingleName }

   constructor TSingleName.Create(n : string);
   begin
      FName := n;
   end;

   function TSingleName.Name : string;
   begin
      Name := FName;
   end;

var
   Names : array [1..2] of TNameBase;
   Index : integer;

begin
   Names[1] := TName.Create('Bob', 'Smith');
   Names[2] := TSingleName.Create('Foo');

   for Index := 1 to 2 do Names[Index].Greet;

end.

Credits

Author: Wtd

Personal tools