Board index » delphi » Write an assert for TP 5.5

Write an assert for TP 5.5

Quote
Chih-Cherng Chin wrote:
> I read the book "Writing Solid Code," now I want to follow advices
> in it to find my programming bugs automatically.  As TP don't come
> with an assert procedure, I need to write one myself.

> The procedure is like the following:

> procedure assert(blAssertExpr : boolean);

> if blAssertExpr is TRUE, it would do nothing.  Otherwise, it would
> print where the assert is called from(file name and line number),
> and halt.  With that information, I can find which assert failed.
> So if I want to make sure certain pointer is not nil, I write

>    assert(p <> nil);

> if p is nil, then assert would print the location of the above
> statement and halt.

> As I use TP 5.5 downloaded from Borland, I don't have its manual.
> Is there any conditional identifier in TP which contains the current
> file name and current line number?  And how could I include those
> information in the Writeln statement?

> Another possible solution is to force the program to show call
> stack when assert fails, but I don't know how to achieve this,
> either.

     Still another possible solution, though perhaps not "elegant",
would be to pass an
additional parameter to "assert" that it would echo if the assertion is
false.  This could be
a simple numeric identifier, or an "information string".

    assert (p <> nil, 1)
or
    assert (p <> nil, 'function MyFunction, line 23')

It would be the programmer's responsibility to put in unique
numbers/statements in such
an "assert" function.  In the first example, it could fail by saying
"Assertion 1 is false", and
the programmer would know to look at the source code for the "assert"
function with
the second parameter of 1.  This sort of routine WILL work with Borland
Pascal, and
with most other Pascal (and other programming language) compilers.

Bob Schor
Pascal Enthusiast

 

Re:Write an assert for TP 5.5


I read the book "Writing Solid Code," now I want to follow advices
in it to find my programming bugs automatically.  As TP don't come
with an assert procedure, I need to write one myself.

The procedure is like the following:

procedure assert(blAssertExpr : boolean);

if blAssertExpr is TRUE, it would do nothing.  Otherwise, it would
print where the assert is called from(file name and line number),
and halt.  With that information, I can find which assert failed.
So if I want to make sure certain pointer is not nil, I write

   assert(p <> nil);

if p is nil, then assert would print the location of the above
statement and halt.

As I use TP 5.5 downloaded from Borland, I don't have its manual.
Is there any conditional identifier in TP which contains the current
file name and current line number?  And how could I include those
information in the Writeln statement?

Another possible solution is to force the program to show call
stack when assert fails, but I don't know how to achieve this,
either.

Please share your knowledge.  Thanks in advance.

--
Chih-Cherng Chin
e...@iii.org.tw

Re:Write an assert for TP 5.5


Quote
Chih-Cherng Chin <c811...@svr5.iii.org.tw> wrote in message

news:7smjud$lc8$1@news.seed.net.tw...

Quote
> I read the book "Writing Solid Code," now I want to follow advices
> in it to find my programming bugs automatically.  As TP don't come
> with an assert procedure, I need to write one myself.

> The procedure is like the following:

> procedure assert(blAssertExpr : boolean);

> if blAssertExpr is TRUE, it would do nothing.  Otherwise, it would
> print where the assert is called from(file name and line number),
> and halt.  With that information, I can find which assert failed.
> So if I want to make sure certain pointer is not nil, I write

>    assert(p <> nil);

> if p is nil, then assert would print the location of the above
> statement and halt.

AFAIR the Solid Code book is mostly (but not exclusively) C. TurboPascal
implementation of Assertions differs from C a bit. You can't implement
Assert as a macro but you can warn the user if the assertion code is
enabled.

You're looking for {$ifdef or {$ifndef. I used to use {$ifndef NOASSERT} so
that I would have assertions unless I explicitly turned them off, but more
recently I use {$ifdef DEBUG} because there was a lot of de{*word*81}y stuff
that I was including on the {$ifdef DEBUG} define and it didn't make sense
to keep them separate.

It's a pain having to put the ifdef comments around every assertion. I put
the entire Interface section of uAssert inside an ifdef condition, so that
any Assert calls compiled outside of an ifdef DEBUG with Debug undefined
will cause a compiler error. At one stage I tried writing an inline assert
routine, so that the ifdefs could be around the body of the routine and
would not be needed elsewhere. But that does not work: the Assert procedure
can be reduced to a NOP by:

Procedure Assert(x:boolean);
{$ifndef DEBUG}
 ... something useful ...
{$else}
inline($90);
{$endif}

but that doesn't help because something like

Assert(Condition);

will still evaluate Condition before executing the NOP. So ISTM we're stuck
with
{$ifdef DEBUG}
Assert(Condition);
{$endif}

You can't get the source file name and line number though. But you can get
the address from which Assert was called, which is nearly as good. The
compiler can find the source file given the address.

asm
  mov ax, [bp+2]
  mov word ptr [o], ax
  mov ax, [bp+4]
  mov word ptr [s], ax
end;
s := s - $10 - PrefixSeg;

where s and o are word variables. This assumes that Procedure Assert has one
boolean parameter. The stuff about PrefixSeg is to subtract the start
address of the program from the segment value, because Error Addresses are
relative to program start. The $10 is the size of the PSP in Paragraphs.

I've got an
  asm
    int 3
  end;
in my Assert procedure where it will be hit if the assertion fails. (You do
NOT want it to be hit if the assertion does not fail!) That way, if you are
running it in the de{*word*81}, the program will stop there and you can look at
the call stack. I follow this with

if DoAbort then
Begin
  Pause; { in case Exit routines clear the screen }
  Halt(255);
End;
DoAbort := True;

DoAbort is a typed constant (static variable) in my uAssert unit which is
initialised to True, if I decide I want to allow the program to continue in
spite of the failed assertion, e.g. to see the value of the local variables
in the routine where Assert got called, I can do that. I cannot remember why
I didn't use the parameter again here. Pause is a local routine which waits
for a the user to press the spacebar.

You do not want to ship code with assertions enabled. Also, if you remove
the DEBUG compiler define and then run Make instead of Build you may have
some TPUs compiled for Debug because they were not modified. TurboPascal
units can have a main section, which gets run once when the program is
starting. I have a main section in uAssert which displays a message saying
that this is debug code and not to be released, and waits for the user to
press the spacebar. That reassures me that uAssert got rebuilt after DEBUG
was undefined. And since the interface section of uAssert is empty if DEBUG
is undefined, the program will not link if there are units with assert calls
unless they are also rebuilt.

Hope some of this is helpful

FP

Finally

Re:Write an assert for TP 5.5


It is possible to implement assertions in Pascal in the same way that
they are implemented in C.  As you suggest, we would like to be able
to put a statement such as "assert(p <> nil)" into the source code,
have it do nothing if p <> nil, and have it display the message
"assertion failure in unit x.pas at line n:  p <> nil" if p = nil.  We
would also like to be able to turn assertions on while we are still
debugging and testing and to turn them off when we are done testing.
(Users should never see an assertion failure, and we don't even want
the cost of a call to a successful assertion function in the final
product.)

C has a built-in preproccessor; Pascal doesn't.  To implement the
assertion function in Pascal, you have to write your own preprocessor.
You embed the assertions as comments in the source file using some
special syntax, such as {#assert(BoolExpr)}.  The preprocessor reads
the source file, keeping track of file names and line numbers, and
expands any such assertions into something like

  if not(BoolExpr) then
    abort(UnitName, LineNum, BoolExprStr);

where the "abort" function just displays the assertion message and
halts, and BoolExprString is the string representation of the
assertion test.

When you are debugging, you process your source files through the
preprocessor before compiling them so that the assertion comments are
expanded into the assertion statements.  For the production version,
you compile the original source files, so that the assertions are just
comments.  (Alternatively, you can tell the preprocessor whether or
not you are in debug mode, and it can strip out the assertions if you
aren't in debug mode, making the resulting source files a little
easier to read.)

It isn't so hard as it might seem to write such a preprocessor.  I
wrote one quite a while ago, which could not only handle assertions as
described above but could do a number of other things, as well.  Let
me know if you would like more information about how to approach such
a project.

On 27 Sep 1999 02:14:37 GMT, Chih-Cherng Chin

Quote
<c811...@svr5.iii.org.tw> wrote:
>I read the book "Writing Solid Code," now I want to follow advices
>in it to find my programming bugs automatically.  As TP don't come
>with an assert procedure, I need to write one myself.

>Please share your knowledge.  Thanks in advance.

Re:Write an assert for TP 5.5


Quote
Steven Sommer wrote:

> It is possible to implement assertions in Pascal in the same way that
> they are implemented in C.  As you suggest, we would like to be able
> to put a statement such as "assert(p <> nil)" into the source code,
> have it do nothing if p <> nil, and have it display the message
>...

Maybe I did not understand the question.

Procedure Assert(......);
{$IFDEF DEBUG}
Begin
  ....
  ....
  ....
End;
{$ELSE}
Begin
End;
{$ENDIF}

The $IFDEF could be placed also in the long code on several places,
to even avoid calling an empty procedure.

:-)
--
Franz Glaser, Glasau 3, A-4191 Vorderweissenbach Austria +43-7219-7035-0
Muehlviertler Elektronik Glaser.  Industrial control and instrumentation
http://members.eunet.at/meg-glaser/    http://members.xoom.com/f_glaser/
http://www.geocities.com/~franzglaser/            http://start.at/bedarf

Re:Write an assert for TP 5.5


JRS:  In article <7smjud$lc...@news.seed.net.tw> of Mon, 27 Sep 1999
02:14:37 in news:comp.lang.pascal.borland, Chih-Cherng Chin

Quote
<c811...@svr5.iii.org.tw> wrote:
>I read the book "Writing Solid Code," now I want to follow advices
>in it to find my programming bugs automatically.  As TP don't come
>with an assert procedure, I need to write one myself.

>The procedure is like the following:

>procedure assert(blAssertExpr : boolean);

>if blAssertExpr is TRUE, it would do nothing.  Otherwise, it would
>print where the assert is called from(file name and line number),
>and halt.  With that information, I can find which assert failed.
>So if I want to make sure certain pointer is not nil, I write

>   assert(p <> nil);

>if p is nil, then assert would print the location of the above
>statement and halt.

It's probably easier to use
        if p<>NIL then RunError(222) ;
the numbers 220-250 should be safe.

The output will give you the location of the RunError call, and the IDE
will go to it.

IIRC, you can then write an ExitProc do that instead of saying
        Runtime error 222 at 0000:0017.
it can say
        Failed Sanity Verification, reference 222, location 0000:0017.

The Assert procedure would need to contain either asm (not 5.5?) or
inline code to get the return address off the stack.  Doubtless many
have done it, with the books; not worth reinventing without.

--
 ? John Stockton, Surrey, UK.  j...@merlyn.demon.co.uk   Turnpike v4.00   MIME. ?
  Web <URL: http://www.merlyn.demon.co.uk/> - FAQish topics, acronyms, & links.
  Correct 4-line sig. separator is as above, a line precisely "-- " (SoRFC1036)
  Do not Mail News to me.    Before a reply, quote with ">" or "> " (SoRFC1036)

Re:Write an assert for TP 5.5


Using conditional compilation is certainly easier than writing a
preprocessor.  The IFDEFs do tend to clutter up the source code,
however, especially if you use them a lot.  The advantage of the
preprocessor is that you can filter all of the assertions out of the
source code for the production version.

The other, more significant advantage of using the preprocessor is
that the assertion-failure messages can display the source file and
line number of where the assertion appeared.  This advantange becomes
quite significant on a large project with many source files.

On Mon, 27 Sep 1999 18:42:19 +0200, "Ing. Franz Glaser"

Quote
<meg-gla...@eunet.at> wrote:

>Procedure Assert(......);
>{$IFDEF DEBUG}
>Begin
>  ....
>  ....
>  ....
>End;
>{$ELSE}
>Begin
>End;
>{$ENDIF}

>The $IFDEF could be placed also in the long code on several places,
>to even avoid calling an empty procedure.

Re:Write an assert for TP 5.5


Ing. Franz Glaser <meg-gla...@eunet.at> wrote in message
news:37EF9E6B.2F150E96@eunet.at...

Quote
> Steven Sommer wrote:

> > It is possible to implement assertions in Pascal in the same way that
> > they are implemented in C.  As you suggest, we would like to be able
> > to put a statement such as "assert(p <> nil)" into the source code,
> > have it do nothing if p <> nil, and have it display the message
> >...

> Maybe I did not understand the question.

> Procedure Assert(......);
> {$IFDEF DEBUG}
> Begin
>   ....
>   ....
>   ....
> End;
> {$ELSE}
> Begin
> End;
> {$ENDIF}

> The $IFDEF could be placed also in the long code on several places,
> to even avoid calling an empty procedure.

Calling the empty procedure would be bad enough, but the real problem with
this is that when you call it, the _parameter_ of Assert will be evaluated
regardless of whether DEBUG is defined. That parameter could be an
expression or a function call, which you do not want to evaluate or call if
DEBUG is not defined - you want the call to Assert to disappear entirely,
including the evaluation of its parameter. I don't know a good way of doing
that other than putting {$IFDEF DEBUG} ... {$endif} around each call to
Assert.

FP

Re:Write an assert for TP 5.5


Quote
Frank Peelo wrote:

> > The $IFDEF could be placed also in the long code on several places,
> > to even avoid calling an empty procedure.

> Calling the empty procedure would be bad enough, but the real problem with
> this is that when you call it, the _parameter_ of Assert will be evaluated
> regardless of whether DEBUG is defined. That parameter could be an
> expression or a function call, which you do not want to evaluate or call if
> DEBUG is not defined - you want the call to Assert to disappear entirely,
> including the evaluation of its parameter. I don't know a good way of doing
> that other than putting {$IFDEF DEBUG} ... {$endif} around each call to
> Assert.

Yes, agreed. If Turb Pascal had something like a macro expander ...
Is there something similar on the net? It would be nice. I am
using TASM alot with REPT etc. to define tables and more or
less $IFDEF - like apps. Some kind of really intelligent
preprocessor. But how to chain that in the IDE? (I like the IDE :-)

Regards,
--
Franz Glaser, Glasau 3, A-4191 Vorderweissenbach Austria +43-7219-7035-0
Muehlviertler Elektronik Glaser.  Industrial control and instrumentation
http://members.eunet.at/meg-glaser/    http://members.xoom.com/f_glaser/
http://www.geocities.com/~franzglaser/            http://start.at/bedarf

Re:Write an assert for TP 5.5


JRS:  In article <37F0F182.A7E9E...@eunet.at> of Tue, 28 Sep 1999
18:49:06 in news:comp.lang.pascal.borland, Ing. Franz Glaser <meg-

Quote
gla...@eunet.at> wrote:

>Yes, agreed. If Turb Pascal had something like a macro expander ...
>Is there something similar on the net? It would be nice. I am
>using TASM alot with REPT etc. to define tables and more or
>less $IFDEF - like apps. Some kind of really intelligent
>preprocessor. But how to chain that in the IDE? (I like the IDE :-)

For "ASSERT" type work, it would be sufficient to put
        {$IFDEF ASSERT} ... {$ENDIF}}
around each assertion and the consequences of its failure.  It would be
manifestly tedious and make the code hard to read.

If one is prepared to give up the use of the IDE's compiler for the
FINAL, Assert-free compilations (and maybe if not), I think I see a way
out.

After carefully scanning all your code { MT /r *.pas {?} } to make sure
that it does not contain the triglyph "{?}", put "{?}" on every line
which is part of an Assert and not part of the final code.

Then, for the final compilation, use Tools to execute a batch file which
uses MT to strip the "{?}" lines outputting into another file and calls
the command-line compiler on that.  H'mmm - or which copies the PAS
files to, say, P##, and strips the PAS.  Or suchlike - including a
proper MakeFile.

Actually, you don't need MT for this; a stripping program is a few lines
of Pascal.

But MT is so very useful that it's worth having anyway :-
        ftp://ftp.demon.co.uk/pub/simtelnet/msdos/txtutl/mintr151.zip
file viewer (ASCII/HEX), string finder, string substituter.  I just
found that URL in the master of my WWW site by "MT pc*.* mintr", for
example; it took an immeasurably small time to find it.

--
 ? John Stockton, Surrey, UK.  j...@merlyn.demon.co.uk   Turnpike v4.00   MIME. ?
  <URL: http://www.merlyn.demon.co.uk/> TP/BP/Delphi/&c. FAQqish topics, links.
  Timo's TurboPascal <A HREF="ftp://garbo.uwasa.fi/pc/link/tsfaqp.zip">FAQ</A>.
  <A HREF="http://www.merlyn.demon.co.uk/clpb-faq.txt">Mini-FAQ</A> of c.l.p.b.

Re:Write an assert for TP 5.5


Dr John Stockton <j...@merlyn.demon.co.uk> wrote in message
news:kZDlqKDXmQ83EwBK@merlyn.demon.co.uk...

Quote
> JRS:  In article <37F0F182.A7E9E...@eunet.at> of Tue, 28 Sep 1999
> For "ASSERT" type work, it would be sufficient to put
>         {$IFDEF ASSERT} ... {$ENDIF}}
> around each assertion and the consequences of its failure.  It would be
> manifestly tedious and make the code hard to read.

> If one is prepared to give up the use of the IDE's compiler for the
> FINAL, Assert-free compilations (and maybe if not), I think I see a way
> out.

> After carefully scanning all your code { MT /r *.pas {?} } to make sure
> that it does not contain the triglyph "{?}", put "{?}" on every line
> which is part of an Assert and not part of the final code.

> Then, for the final compilation, use Tools to execute a batch file which
> uses MT to strip the "{?}" lines outputting into another file and calls
> the command-line compiler on that.  H'mmm - or which copies the PAS
> files to, say, P##, and strips the PAS.  Or suchlike - including a
> proper MakeFile.

No disrespect intended, but to me that sounds worse than using the ifdefs.
They are tedious, true, but ISTM maintaining the triglyphs would be only
marginally less so. They are not too bad as regards making code hard to
read, because they are basically "comments with teeth" stating assumptions
that I was making when I wrote the code - if I didn't have an assert I'd
have a comment of the gummy variety there anyway. And most of them are at
the start/end of procedures anyway, testing parameters and return values for
validity, where they don't interfere with the body of the code.

FP

Re:Write an assert for TP 5.5


Quote
Frank Peelo wrote:

> No disrespect intended, but to me that sounds worse than using the ifdefs.
> They are tedious, true, but ISTM maintaining the triglyphs would be only
> marginally less so. They are not too bad as regards making code hard to
> read, because they are basically "comments with teeth" stating assumptions
> that I was making when I wrote the code - if I didn't have an assert I'd
> have a comment of the gummy variety there anyway. And most of them are at
> the start/end of procedures anyway, testing parameters and return values for
> validity, where they don't interfere with the body of the code.

Some years ago I had a rather diversificated (hope this word is not
too silly) job to program. I used a rather tricky combination of
$IFDEF and $I include, and it was possible to run it in the IDE.
But the macro features would have made it much easier.

Well, most of my problems are related to data structures in this
context, less with coding, procedures etc. I wanted to implement
some kind of "fill in the blanks" approach to use the same basic
code for various hardware configurations (not PC hardware, but
machine control hardware). Eg. a recipe mask, where one plant has
6 silos, another has 24, should be easily configurable. A lot of
things is possible with constants, but sometimes it is tedious in
code, eg. with case statements which should be "optional", not at
runtime, but at compile time. It would be great if for each
implementation a single, customized include file / unit could
define all varieties. Such that further improvements in common
code are easily ported to all versions.

This is easy with FORTH and ASM macros, but not with Pascal.

Regards,
--
Franz Glaser, Glasau 3, A-4191 Vorderweissenbach Austria +43-7219-7035-0
Muehlviertler Elektronik Glaser.  Industrial control and instrumentation
http://members.eunet.at/meg-glaser/    http://members.xoom.com/f_glaser/
http://www.geocities.com/~franzglaser/            http://start.at/bedarf

Re:Write an assert for TP 5.5


On Tue, 28 Sep 1999 19:31:51 +0100, Dr John Stockton

Quote
<j...@merlyn.demon.co.uk> wrote:
>JRS:  In article <37F0F182.A7E9E...@eunet.at> of Tue, 28 Sep 1999
>18:49:06 in news:comp.lang.pascal.borland, Ing. Franz Glaser <meg-
>gla...@eunet.at> wrote:

>For "ASSERT" type work, it would be sufficient to put
>        {$IFDEF ASSERT} ... {$ENDIF}}
>around each assertion and the consequences of its failure.  It would be
>manifestly tedious and make the code hard to read.

>If one is prepared to give up the use of the IDE's compiler for the
>FINAL, Assert-free compilations (and maybe if not), I think I see a way
>out.

>After carefully scanning all your code { MT /r *.pas {?} } to make sure
>that it does not contain the triglyph "{?}", put "{?}" on every line
>which is part of an Assert and not part of the final code.

>Then, for the final compilation, use Tools to execute a batch file which
>uses MT to strip the "{?}" lines outputting into another file and calls
>the command-line compiler on that.  H'mmm - or which copies the PAS
>files to, say, P##, and strips the PAS.  Or suchlike - including a
>proper MakeFile.

>Actually, you don't need MT for this; a stripping program is a few lines
>of Pascal.

I used something similar to this approach on a project consisting of
about 60 units and 100,000 lines of Pascal.  My assertion syntax was

  {#assert(BoolExpr)}

where BoolExpr was any boolean expression, such as p <> nil.  On an
assertion failure, the program would halt and display the message (for
example)

  assertion failure in unit test.pas in line 234:  p <> nil

In order to make this work, I wrote a very simple preprocessor that
would read each line of a source file (the "raw" file) and copy it to
the output (the "cooked" file), escept that it would expand any lines
beginning with "{#assert" into code that would display the above
message.  (As you mention, it is quite easy to write such a
preprocessor.)  The preprocessor also kept track of file names and
line numbers.

I linked the preprocessor into the IDE using the tools menu and
assigned it to shift-F9.  I would load the raw file into the IDE,
press shift-F9 to run the preprocessor on it, then load the resulting
cooked file (or the program that used that unit) into the IDE and
compile and run it.  I could also run the preprocessor from the
command line and tell it to preprocess a list of source files, which
were specified in a text file.

I kept the raw files in one directory and the cooked files in another
directory, so that they wouldn't get mixed up.

Because the assertions were just comments in the raw file, I could
compile the raw files for the production version.

This method was easy to implement and easy to use, and provided the
same functionality as the assert statement in C.  (I actually included
a number of other debugging features in the preprocessor, making it
even better than the C preprocessor, at least for debugging).  I would
be happy to provide more information to anyone who is interested in
this approach.

Other Threads