Skip to main content
Skip table of contents

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.


TEXT
  /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:

TEXT
  {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:

TEXT
  {procedure} /codeblock /basename <<config>> ExecStack0
  {procedure} /codeblock /basename <<config>> savechange dictchange operandchange ExecStack3

The following options may be set in the configuration dictionary:

OnErrorProcedure or nulloptional (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!
TerminateBoolean(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.

ShowStack

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.

StackCheckBlocks

true or array of names

(optional, default = true ) The default value of true means that, if asserts are enabled, then all stack check asserts will be tested and reported. This may lead to an overwhelming amount of data, especially if StackCheckTrack is true. StackCheckBlocks can be set to an array of names, where each name matches a codeblock identifier. Only stack checking with a codeblock that matches one of the items in the array will be tested and reported.

StackCheckError

Boolean

(optional, default = false ) The report from a failed stack check

assertion is usually simply reported as a warning and the job continues. If StackCheckError is true it will trip an error instead. This can occasionally be useful in order to generate a long error report to help understand the context of the failure.

StackCheckTrack

Boolean

(optional, default = false ) If StackCheckTrack is true and assertions are enabled a line will be printed to stderr for every stack checking call encountered, reporting the codeblock , basename and reference for each. Reports will be indented according to the depth of the code call stack.

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.