views:

1166

answers:

9

Since the age of the dinosaurs, Turbo Pascal and nowadays Delphi have a Write() and WriteLn() procedure that quietly do some neat stuff.

  • The number of parameters is variable;

  • Each variable can be of all sorts of types; you can supply integers, doubles, strings, booleans, and mix them all up in any order;

  • You can provide additional parameters for each argument:

Write('Hello':10,'World!':7); // alignment parameters

  • It even shows up in a special way in the code-completion drowdown:
    • Write ([var F:File]; P1; [...,PN] )
    • WriteLn ([var F:File]; [ P1; [...,PN]] )

Now that I was typing this I've noticed that Write and WriteLn don't have the same brackets in the code completion dropdown. Therefore it looks like this was not automatically generated, but it was hard-coded by someone.

Anyway, am I able to write procedures like these myself, or is all of this some magic hardcoded compiler trickery?

+22  A: 

Writeln is what we call a compiler "magic" function. If you look in System.pas, you won't find a Writeln that is declared anything like what you would expect. The compiler literally breaks it all down into individual calls to various special runtime library functions.

In short, there is no way to implement your own version that does all the same things as the built-in writeln without modifying the compiler.

Allen Bauer
In standard pascal, no, but you can come pretty close in Delphi-style variants with variant open array parameters (see my answer, below).
MarkusQ
Yes, there are some tricks you can use, such as the "array of const" technique used by SysUtils.Format(). The OP also mentioned the formatting/alignment parameter syntax, which is clearly impossible using just the language.
Allen Bauer
If I remember correctly there is also the option to write a file driver (within your Delphi program) that allows you to use write/writeln to write to your own sink, e.g. to a logging provider.writeln(logvariable, 'hello ', World);(I did something like this years ago so I am not sure whether that was with Borland Pascal, Virtual Pascal or some Delphi version.)
dummzeuch
+2  A: 

As far as I know, the pascal standards don't include variable arguments.

Having said that, IIRC, GNU Pascal let's you say something like: Procecdure Foo(a: Integer; b: Integer; ...);

Try searching in your compiler's language docs for "Variable Argument Lists" or "conformant arrays". Here's an example of the later: http://www.gnu-pascal.de/demos/conformantdemo.pas.

As the prev poster said, writeln() is magic. I think the problem has to do with how the stack is assembled in a pascal function, but it's been a real long time since I've thought about where things were on the stack :)

However, unless you're writing the "writeln" function (which is already written), you probably don't need to implement a procedure with a variable arguments. Try iteration or recursion instead :)

Seth
It doesn't really have to do with the stack. You couldn't write this function in C, either. Even if you excluded support for the precision and width syntax, you still couldn't do it because there's no way to detect argument types in a varargs list.
Rob Kennedy
+1  A: 

Yes, you can do it in Delphi and friends (e.g. free pascal, Kylix, etc.) but not in more "standard" pascals. Look up variant open array parameters, which are used with a syntax something like this:

procedure MyProc(args : array of const);

(it's been a few years and I don't have manuals hand, so check the details before proceeding). This gives you an open array of TVarData (or something like that) that you can extract RTTI from.

One note though: I don't think you'll be able to match the x:y formatting syntax (that is special), and will probably have to go with a slightly more verbose wrapper.

MarkusQ
Well, technically this is one parameter, isn't it? Since the function/procedure cleans up the stack on exit it needs to know exactly how many parameters it was given. WriteLn() is different as it is not a real procedure.
mghie
@mghie That used to be considered a strong distinction, but with the rise of languages that support currying and VMs that vectorize arguments it isn't so clear. In any case, that's a property of the implementation, not of the language. Either one could be implemented either way.
MarkusQ
+3  A: 

As the Allen said you can't write your own function that does all the same things.

You can, however, write a textfile driver that does something custom and when use standard Write(ln) to write to your textfile driver. We did that in ye old DOS days :)

("Driver" in the context of the previous statement is just a piece of Pascal code that is hooked into the system by switching a pointer in the System unit IIRC. Been a long time since I last used this trick.)

gabr
+1  A: 

Most is already said, but I like to add a few things.

First you can use the Format function. It is great to convert almost any kind of variable to string and control its size. Although it has its flaws:

myvar := 1;
while myvar<10000 do begin
  Memo.Lines.Add(Format('(%3d)', [myVar]));
  myvar := myvar * 10;
end;

Produces:

(  1)
( 10)
(100)
(1000)

So the size is the minimal size (just like the :x:y construction).

To get a minimal amount of variable arguments, you can work with default parameters and overloaded functions:

procedure WriteSome(const A1: string; const A2: string = ''; const A3: string = '');

or

procedure WriteSome(const A1: string); overload;
procedure WriteSome(const A1: Integer); overload;
Gamecat
+1  A: 

You cannot write your own write/writeln in old Pascal. They are generated by the compiler, formatting, justification, etc. That's why some programmers like C language, even the flexible standard functions e.g. printf, scanf, can be implemented by any competent programmers.

You can even create an identical printf function for C if you are inclined to create something more performant than the one implemented by the C vendor. There's no magic trickery in them, your code just need to "walk" the variable arguments.

P.S.

But as MarkusQ have pointed out, some variants of Pascal(Free Pascal, Kylix, etc) can facilitate variable arguments. I last tinker with Pascal, since DOS days, Turbo Pascal 7.

Michael Buen
+1  A: 

Writeln is not "array of const" based, but decomposed by the compiler into various calls that convert the arguments to string and then call the primitive writestring. The "LN" is just a function that writes the lineending as a string. (OS dependant). The procedure variables (function pointers) for the primitives are part of the file type (Textrec/filerec), which is why they can be customized. (e.g. AssignCrt in TP)

If {$I+} mode is on, after each element, a call to the iocheck function is made.

The GPC construct made above is afaik the boundless C open array. FPC (and afaik Delphi too) support this too, but with different syntax.

procedure somehting (a:array of const);cdecl;

will be converted to be ABI compatible to C, printf style. This means that the relevant function (somehting in this case) can't get the number of arguments, but must rely on formatstring parsing. So this is something different from array of const, which is safe.

Marco van de Voort
The cdecl thing would be `function printf(Format: PChar): Integer; cdecl; varargs;` in Delphi (see http://docwiki.embarcadero.com/RADStudio/en/Procedures_and_Functions).
Ulrich Gerhardt
Since 2.4.0 was released on january the 1st, FPC now also supports ISO style read/writestr (which is like read/write(ln) but then to string instead of file). GPC probably supports this too
Marco van de Voort
+1  A: 

It is magic compiler behaviour rather than regular procedure. And no, there is no way to write such subroutines (unfortunately!). Code generation resolves count of actual parameters and their types and translates to appropriate RTL calls (eg. Str()) at compile time. This opposes frequently suggested array of const (single variant array formal parameter, actually) which leads to doing the same at runtime. I'm finding later approach clumsy, it impairs code readability somewhat, and Bugland (Borland/Inprise/Codegear/Embarcadero/name it) broke Code Insight for variant open array constructors (yes, i do care, i use OutputDebugString(PChar(Format('...', [...])))) and code completion does not work properly (or at all) there. So, closest possible way to simulate magic behaviour is to declare lot of overloaded subroutines (really lot of them, one per specific formal parameter type in the specific position). One could call this a kludge too, but this is the only way to get flexibility of variable parameter list and can be hidden in the separate module.

PS: i left out format specifiers aside intentionally, because syntax doesn't allow to semicolons use where Str(), Write() and Writeln() are accepting them.

+1  A: 

Although not a direct answer to you question, I would like to add the following comment: I have recently rewritten some code using Writeln(...) syntax into using a StringList, filling the 'lines' with Format(...) and just plain IntToStr(...), FloatToStr(...) functions and the like.

The main reason for this change was speed improvement. Using a StringList and SaveFileTo is much, much more quicker than the WriteLn, Write combination.

If you are writing a program which creates a lot of text files (I was working on a web site creation program), this makes a lot of difference.

Edelcom