home *** CD-ROM | disk | FTP | other *** search
- 5.0 FOCUS: PROCNAME() and PROCLINE()
- By Greg Lief
-
-
- Introduction
-
- In Summer '87, PROCNAME() and PROCLINE() returned the name of the
- current procedure name and source code line number. However, 5.0
- greatly expands upon that limited use, thanks to the implementation of
- an internal "activation stack".
-
- The Activation Stack
-
- The activation stack keeps track of all calls to procedures,
- functions, and code blocks (we'll come back to these a bit later).
- This enables us to trace our steps at any time during the course of
- our application, and is particularly handy when the beast crashes (a
- familiar occurrence as we make our first steps into the brave new
- world of 5.0). It does not have any apparent limits in terms of
- size (although I suppose that if you have the time to look long
- enough, you will undoubtedly discover any such limits).
-
- Take the following test code (please!):
-
- function main
- private x
- x = 0
- test()
- return 0 && instead of NIL for S'87 compatibility
-
- function test
- x = x + 1 && instead of x++ for S'87 compatibility
- test2()
- return 0 && same as above
-
- function test2
- x = str(x)
- test3()
- return 0 && same as above
-
- function test3
- ? x * 5
- return 0 && same as above
-
- (Note: this is deliberately written using the nasty habit of inherited
- PRIVATE variables so that it will crash. Yes, I actually have to
- force myself to write bugs -- what a world!)
-
- Running this code under Summer '87 presents us with the following:
-
- Proc TEST3, line 18, type mismatch
-
- Quite informative! This is the sort of run-time error that gives
- Clipper programs and programmers a rotten name. Let's run that again,
- this time under 5.0:
-
- Error BASE/1083 Argument error: *
- Called from TEST3(18)
- Called from TEST2(14)
- Called from TEST(9)
- Called from MAIN(4)
-
- Aha! We can see exactly how we got to the offending line of code, and
- thus are far better equipped to squash the bug quickly.
-
- Depth
-
- Each time that you call a procedure or function, the current procedure
- and line number are added to the activation stack. To refer to the
- previous procedure and line number, you would call PROCNAME(1) and
- PROCLINE(1). That parameter indicates how many levels back to look in
- the activation stack. Of course, you can still call PROCNAME() and
- PROCLINE() with no parameters, in which case they will return the
- current procedure and line number (just like the bad old days). But
- the ability to pass the depth parameter is an extremely welcome
- addition.
-
- If you pass a depth parameter for which there is nothing in the
- activation stack, PROCNAME() will return a null character string ("")
- and PROCLINE() will return zero.
-
- Code Blocks
-
- As mentioned above, each time that you invoke a code block, that
- information will be added to the activation stack. Be warned,
- however, that this works a bit differently than the straight
- procedure/function call. Time for another example... this test
- program initializes a code block B (which as you can see is expecting
- a character-type parameter by the presence of the LEN() function) and
- deliberately crashes it by passing it a numeric:
-
- function main
- local b := { | a | len(a) }
- eval(b, 5)
- return nil
-
- (Again, I really enjoy writing bugs on purpose. In fact, I will
- take this to an extreme near the end of this article.)
-
- When we run this test program, voila!
-
- Error BASE/1111 Argument error: LEN
- Called from (b)MAIN(2)
- Called from MAIN(3)
-
- Whoa, what's that (b)MAIN nonsense? Simple... the (b) preface
- indicates that this is a code block. If you glance again at the code
- above, you will see that the code block is indeed initialized at line
- two. When we attempt to EVALuate it at line three, program control
- jumps back to the line at which the code block was initialized. This
- is always how your programs will react when attempting to evaluate a
- code block. In fact, if you step through a program that uses TBROWSE
- with any related code blocks (SkipBlock, GoTop, GoBottom, to name a
- few), you will be amazed and befuddled at how often the program jumps
- around. The actual logic will often bear little resemblance to what
- we have come to expect in a procedural language, but once you get used
- to it, it is not only understandable, but amusing.
-
- When you compile a code block at run-time using the & operator, the
- information stored in the activation stack will not include a line
- number:
-
- function main
- local b := "{ | a | len(a) }", x
- x := &(b) // code block is created here
- eval(x, 5)
- return nil
-
- The output from this program is:
-
- Error BASE/1111 Argument error: LEN
- Called from (b)MAIN(0)
- Called from MAIN(4)
-
- The Big Example
-
- Not only is the accompanying example educational, but it makes a
- terrific April Fool's Joke. It simulates a Clipper 5.0 program crash
- (as if you did not already have enough problems already).
-
- Seriously, though, it demonstrates stepping through the activation
- stack to show all the places we have been. It also shows cursor
- control and relative screen control (using maxrow() and maxcol()) in
- 5.0, which are both quite different from their Summer '87
- counterparts.
-
- The critical piece of code in this example is the DO..WHILE loop:
-
- i := 1
- do while ! empty(procname(i))
- ? "Called from", Trim(procname(i)) + ;
- "(" + ltrim(str(procline(i++))) + ") "
- enddo
-
- We can write this loop condition because we know that PROCNAME() will
- return an empty character string when we hit the end of the activation
- stack. Notice how we cleverly increment the loop counter with the
- post-increment ("++") operator.
-
- CRASH() will clear the screen, display a random error message and the
- contents of the activation stack, and cap it off with a bogus DOS
- prompt. The clincher is that it will then wait for a set number of
- keypresses (default 5) before resuming the application. This is a
- great prank to play on end users who are driving you nuts. You could
- even structure the logic at the entry of your program to accept a
- command-line parameter (for example, "/NC") which would pre-empt the
- CRASH() call:
-
- function main(params)
- local dont_crash := ("/NC" $ upper(params))
- .
- .
- .
- if ! dont_crash
- crash(300) // allow 300 keystrokes at the fake DOS prompt
- endif
-
- Naturally, the user will be unaware of this parameter (because you
- will not document it, right?). They will then run the program and
- have it crash repeatedly. You can walk over, run the program with the
- command-line parameter, and then defy them to show you where it
- crashes.
-
- There is always the possibility that the person you spring this on
- will not appreciate your sense of humor. Therefore, I must whip out
- the following disclaimer: your humble author will NOT be liable for
- consequential, special, indirect, or other similar damages or claims,
- including loss of profits or any other commercial damage. In other
- words, if your victim gets mad and tosses your computer out the
- window, please do not expect me to buy you a new computer!
-