1. Error Handling

Robust error handling is essential for creating reliable PostScript programs. Understanding error types, handling mechanisms, and recovery strategies ensures your code behaves predictably even when things go wrong.

1.1. Overview

PostScript error handling includes:

  • Error types - Syntax, runtime, and resource errors

  • Error detection - Catching and identifying errors

  • Error recovery - Graceful degradation

  • Error reporting - Diagnostic information

  • Defensive programming - Preventing errors

  • Debugging support - Error tracing

Proper error handling makes programs more robust and easier to maintain.

1.2. Error Types

1.2.1. Common PostScript Errors

% stackunderflow: not enough operands
pop  % ERROR: empty stack

% typecheck: wrong operand type
(string) 5 add  % ERROR: can't add string and number

% rangecheck: value out of range
-1 array  % ERROR: negative array size

% undefined: name not found
nonexistentName  % ERROR: undefined

% invalidaccess: operation not permitted
/Times-Roman findfont 0 get  % ERROR: font dict access

1.2.2. Error Categories

Runtime errors:

% Arithmetic errors
1 0 div  % undefinedresult
0 sqrt   % rangecheck (implementation-dependent)

% Resource errors
99999999 array  % VMerror (out of memory)

% File errors
(nonexistent.ps) (r) file  % ioerror

% Syntax errors
{ ( unclosed string  % syntaxerror

1.3. Basic Error Handling

1.3.1. The stopped Operator

Catches errors in code execution:

% stopped: mark proc stopped -> true | false
% Returns true if error occurred, false if successful

{
  % Code that might error
  1 0 div
} stopped {
  % Error occurred
  (Error: Division by zero) print
} {
  % Success
  (Division succeeded) print
} ifelse

1.3.2. Error Context

% stopped leaves error info on stack
{
  undefined_name
} stopped {
  % Stack has error info
  (Error caught) print

  % Clear error info
  $error /newerror false put
} if

1.3.3. Simple Error Handler

/safeExecute {  % proc -> result true | false
  {
    exec
    true
  } stopped {
    pop  % Discard error
    false
  } ifelse
} def

% Usage
{ 5 3 add } safeExecute {
  (Success: ) print =
} {
  (Failed) print
} ifelse

1.4. Error Information

1.4.1. The $error Dictionary

Contains error details:

% Access error information
$error /errorname get  % Name of error
$error /command get    % Command that caused error
$error /newerror get   % Boolean: new error occurred
$error /ostack get     % Operand stack snapshot
$error /estack get     % Execution stack snapshot

1.4.2. Getting Error Details

/getErrorInfo {  % -> errorName commandName
  $error /errorname get
  $error /command get
} def

% Usage
{
  1 0 div
} stopped {
  getErrorInfo
  (Error: ) print exch =string cvs print
  ( in ) print =string cvs print
} if

1.4.3. Stack Trace

/printStackTrace {
  $error /estack known {
    (Execution stack:) print
    $error /estack get {
      ( ) print
      dup type /nametype eq {
        =string cvs print
      } {
        pop (object) print
      } ifelse
    } forall
    () print
  } if
} def

% Usage on error
{
  undefined_name
} stopped {
  printStackTrace
} if

1.5. Advanced Error Handling

1.5.1. Nested Error Handling

/nestedOperation {
  {
    % Outer operation
    {
      % Inner operation that might fail
      1 0 div
    } stopped {
      (Inner error handled) print
      % Handle or re-raise
    } if

    % Continue outer operation
  } stopped {
    (Outer error handled) print
  } if
} def

1.5.2. Error Recovery

/divideWithFallback {  % a b -> result
  2 copy
  {
    div
  } stopped {
    % Division failed, use fallback
    pop pop
    (Division failed, using 0) print
    0
  } ifelse
} def

% Usage
10 2 divideWithFallback =  % 5
10 0 divideWithFallback =  % 0 (fallback)

1.5.3. Retry Logic

/retryOperation {  % proc maxAttempts -> result true | false
  2 dict begin
    /maxAttempts exch def
    /proc exch def
    /attempts 0 def

    {
      /attempts attempts 1 add def

      {
        proc exec
        true
        exit
      } stopped {
        attempts maxAttempts lt {
          (Retry ) print attempts =
        } {
          (Max attempts reached) print
          false
          exit
        } ifelse
      } ifelse
    } loop
  end
} def

% Usage
{
  rand 2147483647 div 0.8 gt {
    (Success!) print
  } {
    stop
  } ifelse
} 5 retryOperation

1.6. Error Prevention

1.6.1. Input Validation

/safeDivide {  % a b -> result
  dup 0 eq {
    pop pop
    (Error: Division by zero) print
    0
  } {
    div
  } ifelse
} def

% Or with error signaling
/checkedDivide {  % a b -> result | error
  dup 0 eq {
    /DivisionByZero cvx  % Signal error
  } {
    div
  } ifelse
} def

1.6.2. Type Checking

/requireType {  % value expectedType -> value
  exch dup type
  3 index ne {
    (Type error: expected ) print
    2 index =string cvs print
    (, got ) print
    =string cvs print
    stop
  } {
    pop pop
  } ifelse
} def

% Usage
/add2 {  % a b -> sum
  /integertype requireType
  exch /integertype requireType
  add
} def

5 10 add2 =      % OK: 15
5 (text) add2    % Error: type mismatch

1.6.3. Range Checking

/requireRange {  % value min max -> value
  2 index 2 index lt 2 index 3 index gt or {
    (Range error: value ) print
    3 index =string cvs print
    ( not in range [) print
    2 index =string cvs print
    (,) print
    1 index =string cvs print
    (]) print
    stop
  } {
    pop pop
  } ifelse
} def

% Usage
/setAlpha {  % alpha(0-1) -> -
  0 1 requireRange
  % ... use alpha
} def

1.7. Custom Error Handling

1.7.1. Custom Error Types

% Define custom errors
/FileNotFoundError { /FileNotFoundError cvx } def
/InvalidDataError { /InvalidDataError cvx } def
/ConfigurationError { /ConfigurationError cvx } def

% Throw custom error
/openFile {  % filename -> file
  dup {
    (r) file
  } stopped {
    pop
    FileNotFoundError
  } ifelse
} def

% Catch custom error
{
  (nonexistent.txt) openFile
} stopped {
  dup /FileNotFoundError eq {
    pop
    (File not found, using default) print
    (%stdin) (r) file
  } {
    % Re-raise other errors
    stop
  } ifelse
} if

1.7.2. Error Context Manager

/ErrorContext {
  20 dict begin
    /context () def
    /handlers 10 dict def

    /setContext {  % contextName -> -
      /context exch def
    } def

    /addHandler {  % errorType handler -> -
      handlers 3 1 roll put
    } def

    /execute {  % proc -> -
      {
        exec
      } stopped {
        % Check for specific handler
        $error /errorname get
        handlers 1 index known {
          handlers exch get exec
        } {
          % Default handler
          pop
          (Error in context: ) print
          context print
          () print
        } ifelse
      } if
    } def

    currentdict
  end
} def

% Usage
ErrorContext /ctx exch def
ctx /setContext (File Processing) exec
ctx /addHandler /ioerror {
  (IO error during file processing) print
} exec

ctx /execute {
  (nonexistent.ps) run
} exec

1.8. Defensive Programming

1.8.1. Assertions

/assert {  % condition message -> -
  exch not {
    (Assertion failed: ) print print
    stop
  } {
    pop
  } ifelse
} def

% Usage
/processArray {  % array -> -
  dup type /arraytype eq
  (Input must be array) assert

  dup length 0 gt
  (Array must not be empty) assert

  % Process array
} def

1.8.2. Preconditions

/withPreconditions {  % proc preconditionProcs -> -
  exch
  % Check all preconditions
  1 index {
    exec not {
      (Precondition failed) print
      stop
    } if
  } forall

  % Execute if all pass
  exec
  pop
} def

% Usage
{
  % Main operation
  1 0 div
} [
  { count 2 ge }  % Enough operands
  { 1 index 0 ne }  % Divisor not zero
] withPreconditions

1.8.3. Postconditions

/withPostconditions {  % proc postconditionProcs -> -
  2 dict begin
    /postconds exch def
    /proc exch def

    % Execute
    proc exec

    % Check postconditions
    postconds {
      exec not {
        (Postcondition failed) print
        stop
      } if
    } forall
  end
} def

1.9. Error Logging

1.9.1. Simple Logger

/ErrorLogger {
  10 dict begin
    /logFile null def
    /enabled true def

    /init {  % filename -> -
      (a) file /logFile exch def
    } def

    /log {  % message level -> -
      enabled {
        logFile null ne {
          logFile ([) writestring
          logFile realtime =string cvs writestring
          logFile (] ) writestring
          logFile exch writestring
          logFile (: ) writestring
          logFile exch writestring
          logFile (\n) writestring
        } if
      } if
    } def

    /logError {  % errorName commandName -> -
      2 dict begin
        /cmd exch def
        /err exch def

        err =string cvs
        ( in ) cmd =string cvs concatstrings exch concatstrings
        (ERROR) log
      end
    } def

    /close {
      logFile null ne {
        logFile closefile
        /logFile null def
      } if
    } def

    currentdict
  end
} def

% Usage
ErrorLogger /logger exch def
logger /init (errors.log) exec

{
  undefined_name
} stopped {
  getErrorInfo logger /logError exec
} if

logger /close exec

1.9.2. Structured Error Reports

/ErrorReport {
  <<
    /timestamp realtime
    /errorName $error /errorname get
    /command $error /command get
    /context (Unknown)
    /severity 5
  >>
} def

/formatErrorReport {  % errorReport -> string
  dup /timestamp get =string cvs
  ( - ) exch concatstrings
  1 index /errorName get =string cvs concatstrings
  ( in ) exch concatstrings
  1 index /command get =string cvs concatstrings
  exch pop
} def

1.10. Practical Error Handling

1.10.1. File Operations with Errors

/safeFileRead {  % filename -> content true | false
  {
    (r) file
    dup 1024 string exch
    readstring pop
    exch closefile
    true
  } stopped {
    pop false
  } ifelse
} def

% Usage
(data.txt) safeFileRead {
  (File content: ) print print
} {
  (Could not read file) print
} ifelse

1.10.2. Network-Style Operations

/fetchData {  % url -> data
  % Simulated network fetch with retry
  3 {
    {
      % Attempt fetch
      rand 2147483647 div 0.7 gt {
        (Data fetched) print
        exit
      } {
        stop
      } ifelse
    } stopped {
      (Retry...) print
    } ifelse
  } repeat
} def

1.10.3. Transaction Pattern

/transaction {  % setupProc mainProc cleanupProc -> -
  3 dict begin
    /cleanup exch def
    /main exch def
    /setup exch def

    % Setup
    setup exec

    % Main operation
    {
      main exec
    } stopped {
      % Error occurred
      (Transaction failed, rolling back) print
      cleanup exec
      stop
    } {
      % Success
      (Transaction succeeded) print
      cleanup exec
    } ifelse
  end
} def

% Usage
{
  % Setup
  /tempData 100 array def
} {
  % Main operation
  % ... work with tempData
} {
  % Cleanup
  /tempData null def
} transaction

1.11. Error Message Formatting

1.11.1. User-Friendly Messages

/ErrorMessages <<
  /stackunderflow (Not enough values on stack)
  /typecheck (Wrong type of value)
  /rangecheck (Value out of acceptable range)
  /undefined (Name or value not found)
  /VMerror (Out of memory)
  /ioerror (File or I/O error)
>> def

/formatError {  % errorName -> message
  ErrorMessages exch 2 copy known {
    get
  } {
    pop
    (Unknown error: ) exch =string cvs concatstrings
  } ifelse
} def

% Usage
{
  1 0 div
} stopped {
  $error /errorname get formatError print
} if

1.11.2. Contextual Error Messages

/ErrorFormatter {
  <<
    /formatWithContext {  % errorName context -> message
      (Error in ) 3 -1 roll concatstrings
      (: ) exch concatstrings
      exch formatError concatstrings
    } bind

    /formatWithDetails {  % errorDict -> message
      dup /errorname get formatError
      (\nCommand: ) exch concatstrings
      1 index /command get =string cvs concatstrings
      (\nContext: ) exch concatstrings
      1 index /context get concatstrings
      exch pop
    } bind
  >>
} def

1.12. Testing Error Handling

1.12.1. Error Test Cases

/testError {  % errorType testProc -> -
  2 dict begin
    /proc exch def
    /expectedError exch def

    {
      proc exec
      (FAIL: No error raised) print
    } stopped {
      $error /errorname get expectedError eq {
        (PASS: Correct error) print
      } {
        (FAIL: Wrong error) print
      } ifelse
    } ifelse
  end
} def

% Usage
/stackunderflow {
  pop  % Empty stack
} testError

/typecheck {
  (string) 5 add
} testError

1.12.2. Error Handling Coverage

/ErrorHandlingTests {
  [
    % Test 1: Division by zero
    {
      1 0 safeDivide
      0 eq
    }

    % Test 2: Invalid input
    {
      (text) /integertype requireType
      false
    } stopped { pop true } if

    % Test 3: File not found
    {
      (nonexistent.txt) safeFileRead
      not
    }
  ]
} def

% Run tests
ErrorHandlingTests {
  exec {
    (PASS) print
  } {
    (FAIL) print
  } ifelse
} forall

1.13. Performance Considerations

1.13.1. Error Handling Overhead

% Minimal overhead for hot paths
/fastOperation {
  % Assume inputs are valid
  % No error checking
  add
} def

% Thorough checking for critical paths
/safeOperation {
  % Validate everything
  /integertype requireType
  exch /integertype requireType
  add
} def

1.13.2. Lazy Error Checking

% Check only when needed
/lazyValidation {  % value -> validatedValue
  dup /validated known not {
    dup validate
    dup /validated true put
  } if
} def

1.14. Best Practices

1.14.1. Fail Fast

% Good: validate early
/processData {  % data -> result
  % Validate immediately
  dup type /arraytype ne {
    /typecheck cvx
  } if

  dup length 0 eq {
    /rangecheck cvx
  } if

  % Process valid data
} def

% Bad: validate late
/processData {
  % ... lots of processing
  % ... then check if valid
} def

1.14.2. Informative Error Messages

% Good: specific message
/divide {  % a b -> a/b
  dup 0 eq {
    (Error: Cannot divide by zero. Operand: ) print
    1 index =
    /undefinedresult cvx
  } {
    div
  } ifelse
} def

% Bad: generic message
/divide {
  dup 0 eq { stop } { div } ifelse
} def

1.14.3. Clean Up Resources

% Good: cleanup on error
/safeOperation {
  /file (data.txt) (r) file def

  {
    % Operation
    file 256 string readline pop
  } stopped {
    file closefile
    stop
  } {
    file closefile
  } ifelse
} def

1.15. Common Pitfalls

1.15.1. Swallowing Errors

% Wrong: hides errors
{
  dangerousOperation
} stopped pop  % Ignores error!

% Correct: handle or log
{
  dangerousOperation
} stopped {
  (Error occurred) print
  % Log or handle appropriately
} if

1.15.2. Incomplete Error Handling

% Wrong: only handles one error type
{
  operation
} stopped {
  $error /errorname get /ioerror eq {
    (IO error) print
  } if
  % Other errors ignored!
} if

% Correct: handle all cases
{
  operation
} stopped {
  $error /errorname get
  dup /ioerror eq {
    (IO error) print
  } {
    dup /VMerror eq {
      (Memory error) print
    } {
      (Other error: ) print =
    } ifelse
  } ifelse
} if

1.15.3. Losing Error Context

% Wrong: no context preserved
{
  {
    operation
  } stopped {
    (Error) print
  } if
} repeat

% Correct: preserve context
{
  {
    operation
  } stopped {
    (Error in iteration ) print
    iterationNumber =
  } if
} repeat

1.16. See Also


Back to top

Copyright © 2025 Ribose. PostScript is a trademark of Adobe. Distributed under the MIT License.