Pascal Whirlwind
From Compsci.ca Wiki
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 open 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