Stack checking with HqnAssert
This page applies to Harlequin v13.1r0 and later; both Harlequin Core and Harlequin MultiRIP
The HqnAssert
procset includes a number of procedures designed to assist in managing the PostScript dictionary and operand stacks and the save level; these are divided into four groups:
Identifying the start of a code context
The StartStackCheck
call is normally used at the start of a procedure or similar size of code element. It takes two operands, both names:
- The
codeblock
– usually the name of the procset or file that includes this call. - The
basename
– usually the name of the procedure, or some other identifiable collection of code.
Neither of these names have any special significance, except that the basename
must be used consistently between the StartStackCheck
call and any associated stack checking calls. The names should be unique and should usually be defined so that your developers can determine which piece of code they are intended to identify, e.g.:
/MyProcSet /AProcedureName /HqnAssert /ProcSet findresource /StartStackCheck get exec
CAUTION: If a piece of code can be called recursively do not use stack checking within that code; add it to the surrounding calling code instead. This is because the basename of each context must be unique within the set of active contexts.
Checking the stack at a point in the code
At any point in the code context started by StartStackCheck,
the following procedures can be used to make an assertion about the state of the save level, and of the depth of the dictionary and operand stacks, relative to their state when StartStackCheck
was called.
StackCheck0
takes two names as operands: (1) The basename
of this code context (which must match the basename
specified to the preceding StartStackCheck
, and (2) a reference for the current point in the code. It asserts that the save level and the depth of the dictionary and operand stacks are the same as they were at StartStackCheck
.
/AProcedureName /BeforeWibbling /HqnAssert /ProcSet findresource /StackCheck0 get exec
StackCheck3
is similar to StackCheck0
except that it takes five arguments, with the last three specifying how the save level, dictionary stack size and operand stack size differ from their state at StartStackCheck
.
/basename /reference savechange dictchange operandchange StackCheck3
In this, savechange
must be an integer or null. Using an integer asserts that the save level is that much larger (smaller for negative numbers) than it was at StartStackCheck
. The save level change is not checked if null is specified.
dictchange
is an integer or null with the same semantics as savechange
, only for the depth of the dictionary stack. No checking of the contents of the stack is done, just the depth.
operandchange
in Harlequin 12 and earlier had the same semantics as dictchange
; it could be an integer or null. As from Harlequin 13 it may also be either an array or a dictionary.
Array form
The value of operandchange may be an array of dictionaries, which make assertions about the top few items of the operand stack. This form does not make any assertions about how the depth of the operand stack has changed since StartStackCheck
. Each dictionary must contain one key, which must be /Type
or /Value
. The value of a /Type
key must be an array of PostScript object types (/integertype, /nametype etc.).
The value of a /Value
key must be an array of objects.
The length of the array argument passed to StackCheck3
determines how many items at the top of the operand stack will be examined. If the array is longer than the depth of the operand stack, then the assertion automatically fails.
The last dictionary in the array will be compared with the topmost item on the operand stack, then the last-but-one dictionary in the array will be compared to the top-but-one item on the operand stack and so on.
If the dictionary contains a Type
key the type of the operand stack item will be compared with the values of the Type
array. If it does not match any of them the assertion has failed.
If the dictionary contains a Value
key the operand stack item will be compared with the values in the Values
array. If it does not match any of them the assertion has failed. Note that comparison of compound objects (arrays and dictionaries) follows standard PostScript rules, which are best considered as comparing pointers to the object rather than the object itself.
The length of the array is not associated in any way with any change in size of the op stack since StartStackCheck
.
Dictionary form
The value of operandchange may be a dictionary, which makes an assertion about the change in the depth of the operand stack since StartStackCheck
, and about the top few items on the operand stack.
Each key in the dictionary must be an integer. When StackCheck3
is encountered the change in the operand stack depth since StartStackCheck
is calculated and the resulting integer is looked for as a key in the dictionary. If that integer is not present, the assertion about stack depth has failed.
If the change integer is found in the dictionary, the value of that key is reviewed. It must be an array in exactly the same format as described above, containing dictionaries which, themselves, contain either a /Type
or /Value
key.
/GetChar17 {% takes a string, returns "char17 true" or "false"
/MyProcSet /GetChar17 StartStackCheck
dup length 17 gt { 17 get true }{ pop false } ifelse
/GetChar17 /GotChar17 0 0 <<
0 [ << /Value [ false ] >> ]
1 [ << /Type [ /integertype ] >> << /Value [ true ] >> ]
>> StackCheck3
…
Closing the code context
At the end of each code context (e.g., each procedure in which StartStackCheck
has been used) it must be closed. As from Harlequin 13 three procedures are provided to do this. In addition, to support backward compatibility, and writing code that works in both v12 and v13, the StackCheck0
and StackCheck3
procedures may be used with the reference /End
.
Failing to close all code contexts that are started with StartStackCheck
will trigger warning messages that are visible even if asserts are not enabled.
EndStackCheck0
is exactly analogous with StackCheck0
, except that no reference should be provided; a reference of /End
is added automatically.
EndStackCheck3
is exactly analogous with StackCheck3
, except that no reference should be provided; a reference of /End
is added automatically.
EndStackCheckNull
is a convenience procedure that takes just a basename
as an operand. All three change operands are set to null. This can be used to close a code context without checking save levels or changes in the dictionary and operand stack depths.
/AProcedureName /HqnAssert /ProcSet findresource /EndStackCheckNull get exec
Safely running a procedure with stack checking
Sometimes it’s desirable to run a piece of code inside a stack checking context and also with error handling for a PostScript error within that procedure. If the procedure is called within deeply nested code that uses stopped contexts any PostScript errors will be hidden, which may be what is intended for production use, but makes code development and maintenance much more difficult.
Two procedures are provided to do this:
ExecSafe0
is used where the procedure being called makes no changes to the dict or op stacks or the save level.
ExecSafe3
is used where it will make such changes:
{procedure} /codeblock /basename ExecStack0
{procedure} /codeblock /basename savechange dictchange operandchange ExecStack3
All operands apart from the procedure are the same as those used for StartStackCheck
and StackCheck3
.
In addition, an optional configuration dictionary may be used:
{procedure} /codeblock /basename <<config>> ExecStack0
{procedure} /codeblock /basename <<config>> savechange dictchange operandchange ExecStack3
The following options may be set in the configuration dictionary:
OnError | Procedure or null | optional (default = null) Defines if and how an error in the procedure is reported. The default value of null equates to a call to defaulthandleerror , otherwise the supplied procedure will be called. Use with care! |
Terminate | Boolean | (optional, default = false ) The default value of false causes a PostScript error in the procedure to trigger HqnAssert to call stop . This is usually the right thing to do so that any surrounding stopped contexts are triggered correctly. In a few cases it is better to ensure that the error triggers the job to be aborted cleanly. This is most common when triggering errors within page device callbacks. |
Stack checking options
The following options may be set in a dictionary provided to OverrideAsserts. All of these options are new in Harlequin 13.
| 0 or positive integer | (optional, default = 0) If a stack check assertion regarding the change in the operand stack depth was incorrect then the top ShowStack items of the operand stack will be shown in the log. |
|
| (optional, default = |
| Boolean | (optional, default = assertion is usually simply reported as a warning and the job continues. If |
| Boolean | (optional, default = |