home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / pascal / library / dos / bix / catch.doc < prev    next >
Text File  |  1986-08-04  |  19KB  |  503 lines

  1.  
  2.  
  3.  
  4.  
  5. (*) Turbo Catch and Throw
  6. (*)
  7. (*) by Andrew Feldstein
  8. (*)
  9. (*) Version 1.0
  10. (*)
  11. (*) December 18, 1985
  12. (*)
  13.  
  14.  
  15. I.  DESCRIPTION
  16.  
  17. A. Usage
  18.  
  19. This section defines the basic use of the procedures CATCH,
  20. THROW, and UNCATCH and the function CAUGHT as supplied in the
  21. module catch.pas.  Succeeding sections contain a more detailed
  22. description.
  23.  
  24. CATCH is called with a parameter of type CATCHDATA.  Often,
  25. this will be global to avoid the inconvenience of passing it as a
  26. parameter to every procedure in the program.
  27.  
  28. A THROW later called on CATCHDATA previously initialized by CATCH
  29. will unwind the stack until the procedure in which the data was
  30. originally initialized is on the top of the stack; control will
  31. be transferred to the statement directly succeeding the call to
  32. CATCH.  THROW can be called on a CATCHDATA any number of times for
  33. any one call to CATCH.
  34.  
  35. CAUGHT is a boolean function which takes a CATCHDATA as a
  36. parameter.  The function returns false if the statement
  37. succeeding the CATCH call was reached through CATCH itself; the
  38. function returns true if the statement was reached via a call to
  39. THROW.
  40.  
  41. UNCATCH should be called by any procedure which calls CATCH
  42. before that procedure exits.  However, this is not strictly
  43. necessary--it simply ensures that it is ERROR for any later THROW
  44. on that particular incarnation of CATCHDATA.
  45.  
  46.  
  47. B.  Overview
  48.  
  49. When CATCH is called the status of the stack and control
  50. variables is remembered.  When THROW is called, the stack is
  51. unwound until the destination procedure's frame is the current
  52. frame and control is transferred to the statement directly
  53. succeeding the prior call to CATCH.
  54.  
  55. Thus, the THROW procedure performs a goto in the sense that any
  56. call to the procedure CATCH can be said to define a label
  57. pointing to the statement succeeding it.  THROW functionally
  58. performs a goto to that label.  However, this goto is not without
  59. restriction as the goto statement proper is.  An unrestricted
  60. goto allows a jump to any location in the program, whether or not
  61. that location lies within the lexical or dynamic scope of the
  62. block in which the goto statement is executed.
  63.  
  64. Lexical scope in Pascal is defined as the currently defined block
  65. and all blocks global to it.  For example, if procedure C is a
  66. sub-procedure of procedure B, procedure B is a sub-procedure of
  67. procedure A, and procedure A is defined in the global program
  68. block, then the lexical scope of procedure B extends to the
  69. declarations (and, for the purposes of goto, the code) of the
  70. global program block, of procedure A, and of procedure B itself.
  71. It does not extend to the declarations within procedure C.  Turbo
  72. Pascal allows goto statements whose target location is within the
  73. current block, but not to any other block.
  74.  
  75. Dynamic scope is defined as the currently executing block, and
  76. all those other blocks that are also currently active--i.e. all
  77. those blocks which through the chain of calling have led to the
  78. current procedure being executed.  For example, given the
  79. definitions of A, B, and C in the previous paragraph, if the
  80. code defined in procedure B is executing, then the dynamic scope
  81. of procedure B AT THAT TIME is B, A, and the global block.
  82.  
  83. The goto performed by THROW is restricted to locations within the
  84. dynamic scope of the currently executing procedure--the procedure
  85. which called CATCH must not have exited when THROW is
  86. called!  Even if the procedure which once called CATCH has exited
  87. and been entered again, it would be error for a THROW on any
  88. CATCHDATA not initialized by the current incarnation of the
  89. procedure calling CATCH.
  90.  
  91. NOTE:  This means, among other things, that CATCH cannot be
  92. conveniently called from within error procedures (for example
  93. those in example.pas).  CATCH ought to be treated as a label.
  94.  
  95. While it is an error for a THROW to be made out of dynamic scope,
  96. there is no convenient way in the Turbo runtime system to check
  97. for this.  However, with judicious use of the UNCATCH procedure,
  98. the user can effect this type of error checking:  if the UNCATCH
  99. procedure is called on any CATCHDATA previously initialized by
  100. CATCH when that CATCHDATA is no longer relevant, subsequent throws
  101. on that CATCHDATA will cause an error.
  102.  
  103. It is often said, "Catch and Throw is a structured form of
  104. goto."  As shown above, it is at least a form of GOTO.  However,
  105. that it is "structured" is true only in the sense that it is
  106. implemented with subroutines and not mere jumps.  Consider:  With
  107. goto proper, one can at least determine by looking at the goto
  108. statement where control will end up.  With a THROW, on the other
  109. hand, because the transfer of control is functionally an indirect
  110. jump one cannot tell.  If there are several places where CATCH
  111. may have been called, then there are several places to which
  112. control may be transferred.  Further, with goto many statements
  113. may jump to a single location, but each jump is to only one
  114. location.  With THROW, not only may many statements jump to a
  115. single location but each jump may be to many locations.  Thus,
  116. the possibility of "spaghetti" code with Catch and Trhow is
  117. ultimately even greater than with goto!
  118.  
  119. Is this then "structured?"  Yes and no.  It is structured if the
  120. possible horribles suggested above are minimized and the power of
  121. the mechanism is reigned in by a few well-defined entry and exit
  122. mechanisms (such as are found in the error procedures of
  123. example.pas).  The following example, however, although illustrative
  124. of how THROW transfers control, is otherwise a good example of bad
  125. use.
  126.  
  127.  
  128. C.  A Minimal Example
  129.  
  130. { The following little program prints out "Strike any key to
  131. exit." until a key is pressed. It then prints out "Goodbye." and
  132. exits.  A more sophisticated example of error processing with
  133. Catch and Throw (and a better example of its use) is supplied in
  134. the file "example.pas."  The example here is meant merely to
  135. demonstrate how Catch and Throw are used. }
  136.  
  137. {$C-}
  138. {$I CATCH.PAS}
  139.  
  140. var cd : catchdata;
  141.  
  142. procedure messagecheck;
  143. var eatchar : char;
  144. begin
  145.    writeln('Strike any key to exit.');
  146.    if keypressed then
  147.       begin
  148.          read(kbd,eatchar);  { Gobble the character pressed. }
  149.          throw(cd)
  150.       end
  151. end;
  152.  
  153. procedure main;
  154. var c : char;
  155. begin
  156.    catch(cd);
  157.    if caught(cd) then
  158.       begin
  159.          writeln('Goodbye.');
  160.          exit
  161.       end;
  162.    repeat
  163.       messagecheck
  164.    until false
  165. end;
  166.  
  167. begin main end.
  168.  
  169.  
  170.  
  171. II.  IMPLEMENTATION
  172.  
  173. A.  Pascal Considerations
  174.  
  175. -- Save data globally or locally:
  176.  
  177. Should CATCH save the necessary data in a single, private
  178. location, or should it save it in a user defined data structure?
  179. As useful as being able to simultaneous throw to only one
  180. location is, it is infinitely more useful to be able to throw to
  181. more than one location at any one time.  (For example an error
  182. procedure can maintain a stack of locations).  Thus, the catch
  183. and throw procedures are implemented with a single parameter
  184. containing the state information necessary to do the throw.
  185.  
  186. -- Implementation of CATCH:
  187.  
  188. Should CATCH be declared as a procedure or function?
  189.  
  190. It is generally useful for the program after a return from the
  191. CATCH procedure to distinguish whether the return was from the
  192. original call to CATCH or because of some THROW. One way to do
  193. this is for a procedure calling CATCH to maintain some sort of
  194. state variable, but this is messy as well as unintuitive and hard
  195. to implement.  Some implementations make CATCH a boolean
  196. function.  The calling procedure invokes it thus:  "if CATCH
  197. then [original call] else [call via THROW]." This has a language
  198. problem and an implementation problem.
  199.  
  200. The language problem is that very often it is unnecessary to to
  201. differentiate among returns from an original call to CATCH and
  202. returns from CATCH via a call to THROW.  It is messy to force a
  203. procedure to declare a boolean variable just to call CATCH:
  204. "dummy_boolean := CATCH."
  205.  
  206. The implementation problem is that if CATCH is a function then
  207. the THROW will necessarily be into a boolean expression while if
  208. CATCH is a procedure then the location it returns to is the
  209. beginning of a statement--a much cleaner concept.  The problem
  210. with jumping to the middle of an expression should be manifest:
  211. If the boolean expression is complex, then the other elements of
  212. the expression will not have been evaluated even though they
  213. ought to have (e.g. ((A=B) AND CATCH)--(A=B) will not have been
  214. evaluated).  Even if the boolean expression is simply a call to
  215. CATCH there is the problem of how a particular implementation
  216. handles function results--it is much easier for the THROW
  217. procedure to simulate return from a procedure than from a
  218. function.
  219.  
  220. The simple solution I have chosen for this problem is to provide
  221. the function CAUGHT (which, incidentally, takes the same type of
  222. parameter as CATCH and THROW) and returns true if the last
  223. procedure called on the particular CATCHDATA was a THROW and false
  224. if it was CATCH.
  225.  
  226. --Checking that CATCH previously called on data passed to THROW:
  227.  
  228. Finally I have provided some error protection for those (
  229. grantedly improper) cases where THROW might be called without a
  230. prior call to CATCH, or where the procedure calling CATCH has
  231. exited.  Both of these ought semantically never to happen, yet
  232. through inadvertence they might.  Therefore,  the CATCH procedure
  233. sets special signature bytes within the data saved so that THROW
  234. may recognize that it has valid data.  This provides a minimal
  235. level of detection of data being inadvertently written over.
  236.  
  237. -- Checking that THROW is within dynamic scope:
  238.  
  239. As a last protection, I have provided the procedure UNCATCH which
  240. operates to invalidate a previous CATCH.  This protects the
  241. system in the case that the data in which the state is saved is
  242. global (very common), the procecdure which called CATCH might
  243. exit removing its frame from the stack, and THROW is errantly
  244. called.  A procedure which calls CATCH should call UNCATCH before
  245. it exits to invoke this protection.  Use of UNCATCH is especially
  246. recommended when used in Turbo's memory compile mode as global
  247. data will remain initialized from a previous running.
  248.  
  249. When THROW is called on data on which either CATCH has never been
  250. called or on which UNCATCH has been called, an error message is
  251. output and the program is halted.  While the current (simple)
  252. errorchecking is sufficient to detect the most blatant errors,
  253. possible further errorchecking should be done to determine the
  254. subtler errors (perhaps a checksum).
  255.  
  256. WARNING:  CATCH and THROW as currently implemented are not
  257. designed to be called from within interrupts even though they
  258. would be useful there (imagine hooking up the ^BREAK interrupt to
  259. THROW, for example).
  260.  
  261. B.  Representation of Environments
  262.  
  263. A Turbo program may loosely be seen to execute within several
  264. environments:
  265.  
  266.     - A control environment
  267.     - A frame environment
  268.     - A global data environment
  269.     - A local data environment
  270.     - A dynamic data environment
  271.  
  272. CATCH remembers a particular state of the control and frame
  273. environments which may then subsequently be restored by THROW.
  274. Thus, to implement CATCH it is sufficient to store the states of
  275. the control and frame environments--and to implement THROW it is
  276. sufficient to restore them.  The problem now arises of how Turbo
  277. defines each of these environments.
  278.  
  279. The Turbo manual in discussing external procedures and inline
  280. statements describes the SP, BP, DS, and SS registers as
  281. "critical" (i.e. should be left unchanged).  Furthermore, it is
  282. obvious that the CS and IP (instruction pointer) registers are
  283. also critical (but their values are always implied in the context
  284. of inline statements and external functions so the manual does
  285. not need to refer to them).  The architecture of the 8088 and the
  286. discussions and examples in the Turbo manual suggest that the
  287. control environment is defined by the CS and IP registers and
  288. that the frame environment is defined by the SS, SP, and BP
  289. registers (with the current frame defined by the SS and BP
  290. registers).
  291.  
  292. Because the DS register does not define either the control or
  293. frame environments it may be safely ignored.  But it may be
  294. ignored for yet another reason:  it never changes.  Neither do
  295. the SS and CS registers which do help define the control and
  296. frame environments.  Thus, SS and CS may be ignored since CATCH
  297. knows that the values will be the same when and if a call to
  298. THROW ever comes about.  It is only necessary to save and restore
  299. the SP, BP, and IP registers to implement Catch and Throw.  (Note
  300. that part of the problem of using CATCH or THROW from within an
  301. interrupt routine is that these assumptions break down.)
  302.  
  303. C.  Implementation of CATCH and THROW
  304.  
  305. The values stored by CATCH are contained with in the appropriate
  306. fields of variables of type CATCHDATA.  Transfers to and from the
  307. registers are done via typed constants (which reside in the code
  308. segment).  The indirection is necessary since I did not want to
  309. "hardwire" the structure of the CATCHDATA into the inline
  310. statements.  By doing it this way, everything can be done by name
  311. and not by numerical offset.
  312.  
  313. Furthermore,  I find that the machine language in the inline
  314. statements is simpler if the code segment is addressed with a
  315. simple offset than if the stack segmente is addressed with an
  316. offset off the base pointer register.
  317.  
  318. Turbo assembles the CATCH procedure which has three typed,
  319. two-byte constants and one VAR parameter as follows:
  320.  
  321.        PUSH BP
  322.        MOV  BP,SP
  323.        PUSH BP
  324.        JMP  BEGIN
  325.        DW   0
  326.        DW   0
  327.        DW   0
  328. BEGIN: [Compiled code]
  329.        JMP  END
  330. END:   MOV  SP,BP
  331.        POP  BP
  332.        RET  0004
  333.  
  334. Before the first statement of compiled code is executed, the stack is
  335. set up as follows (offsets are relative to current BP):
  336.  
  337.        (+04)  parameters
  338.        (+02)  return address
  339.        (+00)  Original BP
  340.        (-02)  Original SP (less 2 because of push of original BP)
  341.        (-04)  top of workarea
  342.  
  343. For procedure CATCH, the return address of the calling procedure is at
  344. BP+02; the BP of the calling procedure is at BP+00; the SP of the calling
  345. procedure is the current value of BP plus 8 bytes (for saved BP return
  346. address, and parameter.  These values are stored in the
  347. corresponding fields of the CATCHDATA.
  348.  
  349. The CATCH procedure is implemented so as to store the actual
  350. values that THROW should restore.  All THROW then has to do is
  351. determine that it has valid data and store the values in the
  352. appropriate registers.  The IP register itself is loaded with a
  353. jump instruction.
  354.  
  355.  
  356.  
  357. $$$$$$$$$$$$$$$$ File:  EXAMPLE.PAS
  358.  
  359.  
  360. {$Icatch.pas}
  361.  
  362.         { Maximum number of catchdata in stack }
  363. const errorstackmax = 50;
  364.         { Turbo string definition for error messages }
  365. type  lstring = string[255];
  366.         { Error stack of catch data }
  367. var   errorstack : array[1..errorstackmax] of ^ catchdata;
  368.         { Initialize stack to empty }
  369. const errorstacksize : integer = 0;
  370.  
  371. {----------------------------------------------------------------}
  372.  
  373. { Push the catchdata on the stack.  Take the address of the
  374. variable parameter with the built in "ptr" function.  The stack
  375. contains only pointers to the catchdata for two reasons--to
  376. minimize space and to keep only one copy of any one catchdata
  377. around. }
  378.  
  379. procedure errorpush({IN}var cd : catchdata);
  380. begin
  381.    if errorstacksize = errorstackmax then begin {Error Stack Overflow} end;
  382.    errorstacksize := succ(errorstacksize);
  383.    errorstack[errorstacksize] := ptr(seg(cd),ofs(cd))
  384. end;
  385.  
  386. {----------------------------------------------------------------}
  387.  
  388. { Pop the topmost catchdata on the stack. }
  389.  
  390. procedure errorpop;
  391. begin
  392.    if errorstacksize = 0 then
  393.       begin {Error Stack Underflow} end
  394.    else
  395.       errorstacksize := pred(errorstacksize)
  396. end;
  397.  
  398. {----------------------------------------------------------------}
  399.  
  400. { Does a throw to the catchdata on the top of the stack while
  401. popping it off. }
  402.  
  403. procedure errorcontinue;
  404. begin
  405.    errorpop;
  406.    throw(errorstack[succ(errorstacksize)]^)
  407. end;
  408.  
  409. {----------------------------------------------------------------}
  410.  
  411. { Writes out error message and does a throw to the catchdata on
  412. the top of the stack while popping it off. }
  413.  
  414. procedure error(errormessage : lstring);
  415. begin
  416.    writeln(con,errormessage);
  417.    errorcontinue
  418. end;
  419.  
  420. {----------------------------------------------------------------}
  421.  
  422. { If the code in this procedure, or any procedure called by this
  423. procedure raises an error, then when THROW is executed, control
  424. will be passed to some ultimate caller of this procedure and the
  425. stack frame for this procedure will have been removed from the
  426. stack. }
  427.  
  428. procedure example1;
  429. begin
  430.    { . . . }
  431. end;
  432.  
  433. {----------------------------------------------------------------}
  434.  
  435. { This procedure represents an example of a situation where it
  436. would be intolerable for any error raised within it to THROW
  437. execution to some ultimate caller of this procedure because a
  438. (possibly) open file would then never be closed ultimately
  439. exhausting the very limited resource of open files.  Another
  440. example would be a situation in which memory was allocated but
  441. not yet used (a throw around that procedure would result in a
  442. dangling pointer).
  443.  
  444. If code represented by the ellipsis raises an error, it will be
  445. caught, the file will be closed and error execution will continue
  446. with the next previous procedure to have called CATCH and
  447. ERRORPUSH.  Note that the CATCH and the ERRORPUSH are set up only
  448. when it is critical that there is no throw around this
  449. procedure--i.e. only when the REWRITE of OUTFIL is successful. }
  450.  
  451. procedure example2;
  452. var outfil : text;
  453.     cd : catchdata;
  454. begin
  455.    assign(outfil,'filename.ext');
  456.    {$I-} rewrite(outfil); {$I+}
  457.    if IOresult <> 0 then
  458.       error('IO error:  cannot write file.');
  459.    catch(cd);
  460.    if not caught(cd) then
  461.       errorpush(cd)  { Return is from catch so push errordata }
  462.    else
  463.       begin          { Return is from throw so close file and continue }
  464.          close(outfil);
  465.          errorcontinue
  466.       end;
  467.  
  468.    { . . . }
  469.  
  470.    errorpop;
  471.    uncatch(cd)
  472. end;
  473.  
  474. {----------------------------------------------------------------}
  475.  
  476. { This procedure unconditionally traps all errors and continues
  477. execution with the statement following ERRORPUSH (ignoring
  478. whether that point was reached via CATCH or THROW).  Thus any
  479. error raised by the code represented by the ellipsis would result
  480. in that code being reexecuted.  A common example of this situation
  481. would be a main command loop.
  482.  
  483. Note that if, as is often the case, throw is only ever to one place,
  484. for example to the top of a main command loop, then stacking the
  485. catchdata is unnecessary and catch and throw should be used in their
  486. raw form. }
  487.  
  488. procedure example3;
  489. var cd : catchdata;
  490. begin
  491.    catch(cd);
  492.    errorpush(cd);
  493.  
  494.    { . . . }
  495.  
  496.    errorpop;
  497.    uncatch(cd)
  498. end;
  499.  
  500.  
  501.  
  502. $$$$$$$$$$$$$$$$ End
  503.