(v13) 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 three 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
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
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 = |