/*
 * Copyright © 2008 Chris Wilson <chris@chris-wilson.co.uk>
 *
 * This library is free software; you can redistribute it and/or
 * modify it either under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation
 * (the "LGPL") or, at your option, under the terms of the Mozilla
 * Public License Version 1.1 (the "MPL"). If you do not alter this
 * notice, a recipient may use your version of this file under either
 * the MPL or the LGPL.
 *
 * You should have received a copy of the LGPL along with this library
 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
 * You should have received a copy of the MPL along with this library
 * in the file COPYING-MPL-1.1
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
 * the specific language governing rights and limitations.
 *
 * The Original Code is the cairo graphics library.
 *
 * The Initial Developer of the Original Code is Chris Wilson.
 *
 * Contributor(s):
 *	Chris Wilson <chris@chris-wilson.co.uk>
 */

#include "cairo-script-private.h"

#include <limits.h> /* INT_MAX */
#include <math.h> /* pow */
#include <stdio.h> /* EOF */
#include <stdint.h> /* for {INT,UINT}*_{MIN,MAX} */
#include <stdlib.h> /* malloc/free */
#include <string.h> /* memset */
#include <assert.h>
#include <zlib.h>

#if HAVE_LZO
#include <lzo/lzo2a.h>
#endif

#define DEBUG_SCAN 0

#if WORDS_BIGENDIAN
#define le16(x) bswap_16 (x)
#define le32(x) bswap_32 (x)
#define be16(x) x
#define be32(x) x
#define to_be32(x) x
#else
#define le16(x) x
#define le32(x) x
#define be16(x) bswap_16 (x)
#define be32(x) bswap_32 (x)
#define to_be32(x) bswap_32 (x)
#endif

/*
 * whitespace:
 * 0 - nul
 * 9 - tab
 * A - LF
 * C - FF
 * D - CR
 *
 * syntax delimiters
 * ( = 28, ) = 29 - literal strings
 * < = 3C, > = 3E - hex/base85 strings, dictionary name
 * [ = 5B, ] = 5D - array
 * { = 7B, } = 7C - procedure
 * / = 5C - literal marker
 * % = 25 - comment
 */

static void
fprintf_obj (FILE *stream, csi_t *ctx, const csi_object_t *obj)
{
    switch (csi_object_get_type (obj)) {
	case CSI_OBJECT_TYPE_NULL:
	    fprintf (stream, "NULL\n");
	    break;

	    /* atomics */
	case CSI_OBJECT_TYPE_BOOLEAN:
	    fprintf (stream, "boolean: %s\n",
		    obj->datum.boolean ? "true" : "false");
	    break;
	case CSI_OBJECT_TYPE_INTEGER:
	    fprintf (stream, "integer: %ld\n", obj->datum.integer);
	    break;
	case CSI_OBJECT_TYPE_MARK:
	    fprintf (stream, "mark\n");
	    break;
	case CSI_OBJECT_TYPE_NAME:
	    fprintf (stream, "name: %s\n", (char *) obj->datum.name);
	    break;
	case CSI_OBJECT_TYPE_OPERATOR:
	    fprintf (stream, "operator: %p\n", obj->datum.ptr);
	    break;
	case CSI_OBJECT_TYPE_REAL:
	    fprintf (stream, "real: %g\n", obj->datum.real);
	    break;

	    /* compound */
	case CSI_OBJECT_TYPE_ARRAY:
	    fprintf (stream, "array\n");
	    break;
	case CSI_OBJECT_TYPE_DICTIONARY:
	    fprintf (stream, "dictionary\n");
	    break;
	case CSI_OBJECT_TYPE_FILE:
	    fprintf (stream, "file\n");
	    break;
	case CSI_OBJECT_TYPE_MATRIX:
	    fprintf (stream, "matrix: [%g %g %g %g %g %g]\n",
		    obj->datum.matrix->matrix.xx,
		    obj->datum.matrix->matrix.yx,
		    obj->datum.matrix->matrix.xy,
		    obj->datum.matrix->matrix.yy,
		    obj->datum.matrix->matrix.x0,
		    obj->datum.matrix->matrix.y0);
	    break;
	case CSI_OBJECT_TYPE_STRING:
	    fprintf (stream, "string: len=%ld, defate=%ld, method=%d\n",
		     obj->datum.string->len, obj->datum.string->deflate, obj->datum.string->method);
	    break;

	    /* cairo */
	case CSI_OBJECT_TYPE_CONTEXT:
	    fprintf (stream, "context\n");
	    break;
	case CSI_OBJECT_TYPE_FONT:
	    fprintf (stream, "font\n");
	    break;
	case CSI_OBJECT_TYPE_PATTERN:
	    fprintf (stream, "pattern\n");
	    break;
	case CSI_OBJECT_TYPE_SCALED_FONT:
	    fprintf (stream, "scaled-font\n");
	    break;
	case CSI_OBJECT_TYPE_SURFACE:
	    fprintf (stream, "surface\n");
	    break;
    }
}

/* takes ownership of obj */
static inline csi_status_t
scan_push (csi_t *ctx, csi_object_t *obj)
{
    return ctx->scanner.push (ctx, obj);
}

static inline csi_status_t
scan_execute (csi_t *ctx, csi_object_t *obj)
{
    return ctx->scanner.execute (ctx, obj);
}

static cairo_status_t
buffer_init (csi_t *ctx, csi_buffer_t *buffer)
{
    cairo_status_t status = CSI_STATUS_SUCCESS;

    buffer->size = 16384;
    buffer->base = _csi_alloc (ctx, buffer->size);
    if (_csi_unlikely (buffer->base == NULL)) {
	status = _csi_error (CSI_STATUS_NO_MEMORY);
	buffer->size = 0;
    }

    buffer->ptr = buffer->base;
    buffer->end = buffer->base + buffer->size;

    return status;
}

static void
buffer_fini (csi_t *ctx, csi_buffer_t *buffer)
{
    _csi_free (ctx, buffer->base);
}

static void
_buffer_grow (csi_t *ctx, csi_scanner_t *scan)
{
    int newsize;
    int offset;
    char *base;

    if (_csi_unlikely (scan->buffer.size > INT_MAX / 2))
	longjmp (scan->jmpbuf,  _csi_error (CSI_STATUS_NO_MEMORY));

    offset = scan->buffer.ptr - scan->buffer.base;
    newsize = scan->buffer.size * 2;
    base = _csi_realloc (ctx, scan->buffer.base, newsize);
    if (_csi_unlikely (base == NULL))
	longjmp (scan->jmpbuf,  _csi_error (CSI_STATUS_NO_MEMORY));

    scan->buffer.base = base;
    scan->buffer.ptr  = base + offset;
    scan->buffer.end  = base + newsize;
    scan->buffer.size = newsize;
}

static inline void
buffer_check (csi_t *ctx, csi_scanner_t *scan, int count)
{
    if (_csi_unlikely (scan->buffer.ptr + count > scan->buffer.end))
	_buffer_grow (ctx, scan);
}

static inline void
buffer_add (csi_buffer_t *buffer, int c)
{
    *buffer->ptr++ = c;
}

static inline void
buffer_reset (csi_buffer_t *buffer)
{
    buffer->ptr = buffer->base;
}

static void
token_start (csi_scanner_t *scan)
{
    buffer_reset (&scan->buffer);
}

static void
token_add (csi_t *ctx, csi_scanner_t *scan, int c)
{
    buffer_check (ctx, scan, 1);
    buffer_add (&scan->buffer, c);
}

static void
token_add_unchecked (csi_scanner_t *scan, int c)
{
    buffer_add (&scan->buffer, c);
}

csi_boolean_t
_csi_parse_number (csi_object_t *obj, const char *s, int len)
{
    int radix = 0;
    long long mantissa = 0;
    int exponent = 0;
    int sign = 1;
    int decimal = -1;
    int exponent_sign = 0;
    const char * const end = s + len;

    switch (*s) {
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
	mantissa = *s - '0';
    case '+':
	break;
    case '-':
	sign = -1;
	break;
    case '.':
	decimal = 0;
	break;
    default:
	return FALSE;
    }

    while (++s < end) {
	if (*s < '0') {
	    if (*s == '.') {
		if (_csi_unlikely (radix))
		    return FALSE;
		if (_csi_unlikely (decimal != -1))
		    return FALSE;
		if (_csi_unlikely (exponent_sign))
		    return FALSE;

		decimal = 0;
	    } else if (*s == '!') {
		if (_csi_unlikely (radix))
		    return FALSE;
		if (_csi_unlikely (decimal != -1))
		    return FALSE;
		if (_csi_unlikely (exponent_sign))
		    return FALSE;

		radix = mantissa;
		mantissa = 0;

		if (_csi_unlikely (radix < 2 || radix > 36))
		    return FALSE;
	    } else
		return FALSE;
	} else if (*s <= '9') {
	    int v = *s - '0';
	    if (_csi_unlikely (radix && v >= radix))
		return FALSE;

	    if (exponent_sign) {
		exponent = 10 * exponent + v;
	    } else {
		if (radix)
		    mantissa = radix * mantissa + v;
		else
		    mantissa = 10 * mantissa + v;
		if (decimal != -1)
		    decimal++;
	    }
	} else if (*s == 'E' || * s== 'e') {
	    if (radix == 0) {
		if (_csi_unlikely (s + 1 == end))
		    return FALSE;

		exponent_sign = 1;
		if (s[1] == '-') {
		    exponent_sign = -1;
		    s++;
		} else if (s[1] == '+')
		    s++;
	    } else {
		int v = 0xe;

		if (_csi_unlikely (v >= radix))
		    return FALSE;

		mantissa = radix * mantissa + v;
	    }
	} else if (*s < 'A') {
	    return FALSE;
	} else if (*s <= 'Z') {
	    int v = *s - 'A' + 0xA;

	    if (_csi_unlikely (v >= radix))
		return FALSE;

	    mantissa = radix * mantissa + v;
	} else if (*s < 'a') {
	    return FALSE;
	} else if (*s <= 'z') {
	    int v = *s - 'a' + 0xa;

	    if (_csi_unlikely (v >= radix))
		return FALSE;

	    mantissa = radix * mantissa + v;
	} else
	    return FALSE;
    }

    if (exponent_sign || decimal != -1) {
	if (mantissa == 0) {
	    obj->type = CSI_OBJECT_TYPE_REAL;
	    obj->datum.real = 0.;
	    return TRUE;
	} else {
	    int e;
	    double v;

	    v = mantissa;
	    e = exponent * exponent_sign;
	    if (decimal != -1)
		e -= decimal;
	    switch (e) {
	    case -7: v *= 0.0000001; break;
	    case -6: v *= 0.000001; break;
	    case -5: v *= 0.00001; break;
	    case -4: v *= 0.0001; break;
	    case -3: v *= 0.001; break;
	    case -2: v *= 0.01; break;
	    case -1: v *= 0.1; break;
	    case  0: break;
	    case  1: v *= 10; break;
	    case  2: v *= 100; break;
	    case  3: v *= 1000; break;
	    case  4: v *= 10000; break;
	    case  5: v *= 100000; break;
	    case  6: v *= 1000000; break;
	    default:
		    v *= pow (10, e); /* XXX */
		    break;
	    }

	    obj->type = CSI_OBJECT_TYPE_REAL;
	    obj->datum.real = sign * v;
	    return TRUE;
	}
    } else {
	obj->type = CSI_OBJECT_TYPE_INTEGER;
	obj->datum.integer = sign * mantissa;
	return TRUE;
    }
}

static void
token_end (csi_t *ctx, csi_scanner_t *scan, csi_file_t *src)
{
    cairo_status_t status;
    char *s;
    csi_object_t obj;
    int len;

    /*
     * Any token that consists entirely of regular characters and
     * cannot be interpreted as a number is treated as a name object
     * (more precisely, an executable name). All characters except
     * delimiters and white-space characters can appear in names,
     * including characters ordinarily considered to be punctuation.
     */

    if (_csi_unlikely (scan->buffer.ptr == scan->buffer.base))
	return;

    s = scan->buffer.base;
    len = scan->buffer.ptr - scan->buffer.base;

    if (_csi_likely (! scan->bind)) {
	if (s[0] == '{') { /* special case procedures */
	    if (scan->build_procedure.type != CSI_OBJECT_TYPE_NULL) {
		status = _csi_stack_push (ctx,
					  &scan->procedure_stack,
					  &scan->build_procedure);
		if (_csi_unlikely (status))
		    longjmp (scan->jmpbuf, status);
	    }

	    status = csi_array_new (ctx, 0, &scan->build_procedure);
	    if (_csi_unlikely (status))
		longjmp (scan->jmpbuf, status);

	    scan->build_procedure.type |= CSI_OBJECT_ATTR_EXECUTABLE;
	    return;
	} else if (s[0] == '}') {
	    if (_csi_unlikely
		(scan->build_procedure.type == CSI_OBJECT_TYPE_NULL))
	    {
		longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT));
	    }

	    if (scan->procedure_stack.len) {
		csi_object_t *next;

		next = _csi_stack_peek (&scan->procedure_stack, 0);
		status = csi_array_append (ctx, next->datum.array,
					   &scan->build_procedure);
		scan->build_procedure = *next;
		scan->procedure_stack.len--;
	    } else {
		status = scan_push (ctx, &scan->build_procedure);
		scan->build_procedure.type = CSI_OBJECT_TYPE_NULL;
	    }
	    if (_csi_unlikely (status))
		longjmp (scan->jmpbuf, status);

	    return;
	}
    }

    if (s[0] == '/') {
	if (len >= 2 && s[1] == '/') { /* substituted name */
	    status = csi_name_new (ctx, &obj, s + 2, len - 2);
	    if (_csi_unlikely (status))
		longjmp (scan->jmpbuf, status);

	    status = _csi_name_lookup (ctx, obj.datum.name, &obj);
	} else { /* literal name */
	    status = csi_name_new (ctx, &obj, s + 1, len - 1);
	}
	if (_csi_unlikely (status))
	    longjmp (scan->jmpbuf, status);
    } else {
	if (! _csi_parse_number (&obj, s, len)) {
	    status = csi_name_new (ctx, &obj, s, len);
	    if (_csi_unlikely (status))
		longjmp (scan->jmpbuf, status);

	    obj.type |= CSI_OBJECT_ATTR_EXECUTABLE;
	}
    }

    /* consume whitespace after token, before calling the interpreter */
    if (scan->build_procedure.type != CSI_OBJECT_TYPE_NULL) {
	status = csi_array_append (ctx,
				   scan->build_procedure.datum.array,
				   &obj);
    } else if (obj.type & CSI_OBJECT_ATTR_EXECUTABLE) {
	status = scan_execute (ctx, &obj);
	csi_object_free (ctx, &obj);
    } else {
	status = scan_push (ctx, &obj);
    }
    if (_csi_unlikely (status))
	longjmp (scan->jmpbuf, status);
}

static void
string_add (csi_t *ctx, csi_scanner_t *scan, int c)
{
    buffer_check (ctx, scan, 1);
    buffer_add (&scan->buffer, c);
}

static void
string_end (csi_t *ctx, csi_scanner_t *scan)
{
    csi_object_t obj;
    cairo_status_t status;

    status = csi_string_new (ctx,
			     &obj,
			     scan->buffer.base,
			     scan->buffer.ptr - scan->buffer.base);
    if (_csi_unlikely (status))
	longjmp (scan->jmpbuf, status);

    if (scan->build_procedure.type != CSI_OBJECT_TYPE_NULL)
	status = csi_array_append (ctx,
				   scan->build_procedure.datum.array,
				   &obj);
    else
	status = scan_push (ctx, &obj);
    if (_csi_unlikely (status))
	longjmp (scan->jmpbuf, status);
}

static int
hex_value (int c)
{
    if (c < '0')
	return EOF;
    if (c <= '9')
	return c - '0';
    c |= 32;
    if (c < 'a')
	return EOF;
    if (c <= 'f')
	return c - 'a' + 0xa;
    return EOF;
}

static void
hex_add (csi_t *ctx, csi_scanner_t *scan, int c)
{
    if (scan->accumulator_count == 0) {
	scan->accumulator |= hex_value (c) << 4;
	scan->accumulator_count = 1;
    } else {
	scan->accumulator |= hex_value (c) << 0;
	buffer_check (ctx, scan, 1);
	buffer_add (&scan->buffer, scan->accumulator);

	scan->accumulator = 0;
	scan->accumulator_count = 0;
    }
}

static void
hex_end (csi_t *ctx, csi_scanner_t *scan)
{
    csi_object_t obj;
    cairo_status_t status;

    if (scan->accumulator_count)
	hex_add (ctx, scan, '0');

    status = csi_string_new (ctx,
			     &obj,
			     scan->buffer.base,
			     scan->buffer.ptr - scan->buffer.base);
    if (_csi_unlikely (status))
	longjmp (scan->jmpbuf, status);

    if (scan->build_procedure.type != CSI_OBJECT_TYPE_NULL)
	status = csi_array_append (ctx,
				   scan->build_procedure.datum.array,
				   &obj);
    else
	status = scan_push (ctx, &obj);
    if (_csi_unlikely (status))
	longjmp (scan->jmpbuf, status);
}

static void
base85_add (csi_t *ctx, csi_scanner_t *scan, int c)
{
    if (c == 'z') {
	if (_csi_unlikely (scan->accumulator_count != 0))
	    longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT));

	buffer_check (ctx, scan, 4);
	buffer_add (&scan->buffer, 0);
	buffer_add (&scan->buffer, 0);
	buffer_add (&scan->buffer, 0);
	buffer_add (&scan->buffer, 0);
    } else if (_csi_unlikely (c < '!' || c > 'u')) {
	longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT));
    } else {
	scan->accumulator = scan->accumulator*85 + c - '!';
	if (++scan->accumulator_count == 5) {
	    buffer_check (ctx, scan, 4);
	    buffer_add (&scan->buffer, (scan->accumulator >> 24) & 0xff);
	    buffer_add (&scan->buffer, (scan->accumulator >> 16) & 0xff);
	    buffer_add (&scan->buffer, (scan->accumulator >>  8) & 0xff);
	    buffer_add (&scan->buffer, (scan->accumulator >>  0) & 0xff);

	    scan->accumulator = 0;
	    scan->accumulator_count = 0;
	}
    }
}

static void
base85_end (csi_t *ctx, csi_scanner_t *scan, cairo_bool_t deflate)
{
    csi_object_t obj;
    cairo_status_t status;

    buffer_check (ctx, scan, 4);

    switch (scan->accumulator_count) {
    case 0:
	break;
    case 1:
	longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT));
	break;

    case 2:
	scan->accumulator = scan->accumulator * (85*85*85) + 85*85*85 -1;
	buffer_add (&scan->buffer, (scan->accumulator >> 24) & 0xff);
	break;
    case 3:
	scan->accumulator = scan->accumulator * (85*85) + 85*85 -1;
	buffer_add (&scan->buffer, (scan->accumulator >> 24) & 0xff);
	buffer_add (&scan->buffer, (scan->accumulator >> 16) & 0xff);
	break;
    case 4:
	scan->accumulator = scan->accumulator * 85 + 84;
	buffer_add (&scan->buffer, (scan->accumulator >> 24) & 0xff);
	buffer_add (&scan->buffer, (scan->accumulator >> 16) & 0xff);
	buffer_add (&scan->buffer, (scan->accumulator >>  8) & 0xff);
	break;
    }

    if (deflate) {
        uLongf len = be32 (*(uint32_t *) scan->buffer.base);
	Bytef *source = (Bytef *) (scan->buffer.base + sizeof (uint32_t));

	status = csi_string_deflate_new (ctx, &obj,
					 source,
					 (Bytef *) scan->buffer.ptr - source,
					 len);
	if (_csi_unlikely (status))
	    longjmp (scan->jmpbuf, status);
    } else {
	status = csi_string_new (ctx,
				 &obj,
				 scan->buffer.base,
				 scan->buffer.ptr - scan->buffer.base);
	if (_csi_unlikely (status))
	    longjmp (scan->jmpbuf, status);
    }

    if (scan->build_procedure.type != CSI_OBJECT_TYPE_NULL)
	status = csi_array_append (ctx,
				   scan->build_procedure.datum.array,
				   &obj);
    else
	status = scan_push (ctx, &obj);
    if (_csi_unlikely (status))
	longjmp (scan->jmpbuf, status);
}

static void
base64_add (csi_t *ctx, csi_scanner_t *scan, int c)
{
    int val;

    /* Convert Base64 character to its 6 bit nibble */
    val = scan->accumulator;
    if (c =='/') {
	val = (val << 6) | 63;
    } else if (c =='+') {
	val = (val << 6) | 62;
    } else if (c >='A' && c <='Z') {
	val = (val << 6) | (c -'A');
    } else if (c >='a' && c <='z') {
	val = (val << 6) | (c -'a' + 26);
    } else if (c >='0' && c <='9') {
	val = (val << 6) | (c -'0' + 52);
    }

    buffer_check (ctx, scan, 1);
    switch (scan->accumulator_count++) {
    case 0:
	break;

    case 1:
	buffer_add (&scan->buffer, (val >> 4) & 0xFF);
	val &= 0xF;
	break;

    case 2:
	buffer_add (&scan->buffer, (val >> 2) & 0xFF);
	val &= 0x3;
	break;

    case 3:
	buffer_add (&scan->buffer, (val >> 0) & 0xFF);
	scan->accumulator_count = 0;
	val = 0;
	break;
    }

     if (c == '=') {
	scan->accumulator_count = 0;
	scan->accumulator = 0;
     } else {
	 scan->accumulator = val;
     }
}

static void
base64_end (csi_t *ctx, csi_scanner_t *scan)
{
    csi_object_t obj;
    cairo_status_t status;

    switch (scan->accumulator_count) {
    case 0:
	break;
    case 2:
	base64_add (ctx, scan, (scan->accumulator << 2) & 0x3f);
	base64_add (ctx, scan, '=');
	break;
    case 1:
	base64_add (ctx, scan, (scan->accumulator << 4) & 0x3f);
	base64_add (ctx, scan, '=');
	base64_add (ctx, scan, '=');
	break;
    }

    status = csi_string_new (ctx,
			     &obj,
			     scan->buffer.base,
			     scan->buffer.ptr - scan->buffer.base);
    if (_csi_unlikely (status))
	longjmp (scan->jmpbuf, status);

    if (scan->build_procedure.type != CSI_OBJECT_TYPE_NULL)
	status = csi_array_append (ctx,
				   scan->build_procedure.datum.array,
				   &obj);
    else
	status = scan_push (ctx, &obj);
    if (_csi_unlikely (status))
	longjmp (scan->jmpbuf, status);
}

static void
scan_read (csi_scanner_t *scan, csi_file_t *src, void *ptr, int len)
{
    uint8_t *data = ptr;
    do {
	int ret = csi_file_read (src, data, len);
	if (_csi_unlikely (ret == 0))
	    longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_READ_ERROR));
	data += ret;
	len -= ret;
    } while (_csi_unlikely (len));
}

static void
string_read (csi_t *ctx,
	     csi_scanner_t *scan,
	     csi_file_t *src,
	     int len,
	     int compressed,
	     csi_object_t *obj)
{
    csi_status_t status;

    status = csi_string_new (ctx, obj, NULL, len);
    if (_csi_unlikely (status))
	longjmp (scan->jmpbuf, status);

    if (compressed) {
	uint32_t u32;
	scan_read (scan, src, &u32, 4);
	obj->datum.string->deflate = be32 (u32);
	obj->datum.string->method = compressed;
    }

    if (_csi_likely (len))
	scan_read (scan, src, obj->datum.string->string, len);
    obj->datum.string->string[len] = '\0';
}

static void
_scan_file (csi_t *ctx, csi_file_t *src)
{
    csi_scanner_t *scan = &ctx->scanner;
    int c, next;
    union {
	int8_t i8;
	uint8_t u8;
	int16_t i16;
	uint16_t u16;
	int32_t i32;
	uint32_t u32;
	float f;
    } u;
    int deflate = 0;
    int string_p;

scan_none:
    while ((c = csi_file_getc (src)) != EOF) {
	csi_object_t obj = { CSI_OBJECT_TYPE_NULL };

	switch (c) {
	case 0xa:
	    scan->line_number++;
	case 0x0:
	case 0x9:
	case 0xc:
	case 0xd:
	case 0x20: /* ignore whitespace */
	    break;

	case '%':
	    goto scan_comment;

	case '(':
	    goto scan_string;

	case '[': /* needs special case */
	case ']':
	case '{':
	case '}':
	    token_start (scan);
	    token_add_unchecked (scan, c);
	    token_end (ctx, scan, src);
	    goto scan_none;

	case '<':
	    next = csi_file_getc (src);
	    switch (next) {
	    case EOF:
		csi_file_putc (src, '<');
		return;
	    case '<':
		/* dictionary name */
		token_start (scan);
		token_add_unchecked (scan, '<');
		token_add_unchecked (scan, '<');
		token_end (ctx, scan, src);
		goto scan_none;
	    case '|':
		deflate = 1;
	    case '~':
		goto scan_base85;
	    case '{':
		goto scan_base64;
	    default:
		csi_file_putc (src, next);
		goto scan_hex;
	    }
	    break;

	    /* binary token */
#define MSB_INT8 128
#define MSB_UINT8 129
#define MSB_INT16 130
#define MSB_UINT16 131
#define MSB_INT32 132
#define LSB_INT8 MSB_INT8
#define LSB_UINT8 MSB_UINT8
#define LSB_INT16 133
#define LSB_UINT16 134
#define LSB_INT32 135
#define MSB_FLOAT32 140
#define LSB_FLOAT32 141
	case MSB_INT8:
	    scan_read (scan, src, &u.i8, 1);
	    csi_integer_new (&obj, u.i8);
	    break;
	case MSB_UINT8:
	    scan_read (scan, src, &u.u8, 1);
	    csi_integer_new (&obj, u.u8);
	    break;
	case MSB_INT16:
	    scan_read (scan, src, &u.i16, 2);
	    csi_integer_new (&obj, be16 (u.i16));
	    break;
	case LSB_INT16:
	    scan_read (scan, src, &u.i16, 2);
	    csi_integer_new (&obj, le16 (u.i16));
	    break;
	case MSB_UINT16:
	    scan_read (scan, src, &u.u16, 2);
	    csi_integer_new (&obj, be16 (u.u16));
	    break;
	case LSB_UINT16:
	    scan_read (scan, src, &u.u16, 2);
	    csi_integer_new (&obj, le16 (u.u16));
	    break;
	case MSB_INT32:
	    scan_read (scan, src, &u.i32, 4);
	    csi_integer_new (&obj, be32 (u.i32));
	    break;
	case LSB_INT32:
	    scan_read (scan, src, &u.i32, 4);
	    csi_integer_new (&obj, le32 (u.i32));
	    break;

	case 136: /* 16.16 msb */
	    scan_read (scan, src, &u.i32, 4);
	    csi_real_new (&obj, be32 (u.i32) / 65536.);
	    break;
	case 137: /* 16.16 lsb */
	    scan_read (scan, src, &u.i32, 4);
	    csi_real_new (&obj, le32 (u.i32) / 65536.);
	    break;
	case 138: /* 24.8 msb */
	    scan_read (scan, src, &u.i32, 4);
	    csi_real_new (&obj, be32 (u.i32) / 256.);
	    break;
	case 139: /* 24.8 lsb */
	    scan_read (scan, src, &u.i32, 4);
	    csi_real_new (&obj, le32 (u.i32) / 256.);
	    break;
	case MSB_FLOAT32:
	    scan_read (scan, src, &u.i32, 4);
	    u.i32 = be32 (u.i32);
	    csi_real_new (&obj, u.f);
	    break;
	case LSB_FLOAT32:
	    scan_read (scan, src, &u.i32, 4);
	    u.i32 = le32 (u.i32);
	    csi_real_new (&obj, u.f);
	    break;

#define STRING_1 142
#define STRING_2_MSB 144
#define STRING_2_LSB 146
#define STRING_4_MSB 148
#define STRING_4_LSB 150
#define STRING_DEFLATE 1
	case STRING_1:
	case STRING_1 | STRING_DEFLATE:
	    scan_read (scan, src, &u.u8, 1);
	    string_read (ctx, scan, src, u.u8, c & STRING_DEFLATE, &obj);
	    break;
	case STRING_2_MSB:
	case STRING_2_MSB | STRING_DEFLATE:
	    scan_read (scan, src, &u.u16, 2);
	    string_read (ctx, scan, src, be16 (u.u16),  c & STRING_DEFLATE, &obj);
	    break;
	case STRING_2_LSB:
	case STRING_2_LSB | STRING_DEFLATE:
	    scan_read (scan, src, &u.u16, 2);
	    string_read (ctx, scan, src, le16 (u.u16), c & STRING_DEFLATE, &obj);
	    break;
	case STRING_4_MSB:
	case STRING_4_MSB | STRING_DEFLATE:
	    scan_read (scan, src, &u.u32, 4);
	    string_read (ctx, scan, src, be32 (u.u32), c & STRING_DEFLATE, &obj);
	    break;
	case STRING_4_LSB:
	case STRING_4_LSB | STRING_DEFLATE:
	    scan_read (scan, src, &u.u32, 4);
	    string_read (ctx, scan, src, le32 (u.u32), c & STRING_DEFLATE, &obj);
	    break;

#define OPCODE 152
	case OPCODE:
	    scan_read (scan, src, &u.u8, 1);
	    csi_operator_new (&obj, ctx->opcode[u.u8]);
	    break;

	case OPCODE | 1:
	    scan_read (scan, src, &u.u8, 1);
	    csi_operator_new (&obj, ctx->opcode[u.u8]);
	    obj.type &= ~CSI_OBJECT_ATTR_EXECUTABLE;
	    break;

#define STRING_LZO 154
	case STRING_LZO:
	    scan_read (scan, src, &u.u32, 4);
	    string_read (ctx, scan, src, be32 (u.u32), LZO, &obj);
	    break;

	    /* unassigned */
	case 155:
	case 156:
	case 157:
	case 158:
	case 159:
	    longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT));

	case '#': /* PDF 1.2 escape code */
	    {
		int c_hi = csi_file_getc (src);
		int c_lo = csi_file_getc (src);
		c = (hex_value (c_hi) << 4) | hex_value (c_lo);
	    }
	    /* fall-through */
	default:
	    token_start (scan);
	    token_add_unchecked (scan, c);
	    goto scan_token;
	}

	if (obj.type != CSI_OBJECT_TYPE_NULL) {
	    cairo_status_t status;

	    if (scan->build_procedure.type != CSI_OBJECT_TYPE_NULL) {
		status = csi_array_append (ctx,
					   scan->build_procedure.datum.array,
					   &obj);
	    } else if (obj.type & CSI_OBJECT_ATTR_EXECUTABLE) {
		status = scan_execute (ctx, &obj);
		csi_object_free (ctx, &obj);
	    } else {
		status = scan_push (ctx, &obj);
	    }
	    if (_csi_unlikely (status))
		longjmp (scan->jmpbuf, status);
	}
    }
    return;

scan_token:
    while ((c = csi_file_getc (src)) != EOF) {
	switch (c) {
	case 0xa:
	    scan->line_number++;
	case 0x0:
	case 0x9:
	case 0xc:
	case 0xd:
	case 0x20:
	    token_end (ctx, scan, src);
	    goto scan_none;

	    /* syntax delimiters */
	case '%':
	    token_end (ctx, scan, src);
	    goto scan_comment;
	    /* syntax error? */
	case '(':
	    token_end (ctx, scan, src);
	    goto scan_string;
	    /* XXX syntax error? */
	case ')':
	    token_end (ctx, scan, src);
	    goto scan_none;
	case '/':
	    /* need to special case '^//?' */
	    if (scan->buffer.ptr > scan->buffer.base+1 ||
		scan->buffer.base[0] != '/')
	    {
		token_end (ctx, scan, src);
		token_start (scan);
	    }
	    token_add_unchecked (scan, '/');
	    goto scan_token;

	case '{':
	case '}':
	case ']':
	    token_end (ctx, scan, src);
	    token_start (scan);
	    token_add_unchecked (scan, c);
	    token_end (ctx, scan, src);
	    goto scan_none;

	case '<':
	    csi_file_putc (src, '<');
	    token_end (ctx, scan, src);
	    goto scan_none;

	case '#': /* PDF 1.2 escape code */
	    {
		int c_hi = csi_file_getc (src);
		int c_lo = csi_file_getc (src);
		c = (hex_value (c_hi) << 4) | hex_value (c_lo);
	    }
	    /* fall-through */
	default:
	    token_add (ctx, scan, c);
	    break;
	}
    }
    token_end (ctx, scan, src);
    return;

scan_comment:
    /* discard until newline */
    while ((c = csi_file_getc (src)) != EOF) {
	switch (c) {
	case 0xa:
	    scan->line_number++;
	case 0xc:
	    goto scan_none;
	}
    }
    return;

scan_string:
    buffer_reset (&scan->buffer);
    string_p = 1;
    while ((c = csi_file_getc (src)) != EOF) {
	switch (c) {
	case '\\': /* escape */
	    next = csi_file_getc (src);
	    switch (next) {
	    case EOF:
		longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT));

	    case 'n':
		string_add (ctx, scan, '\n');
		break;
	    case 'r':
		string_add (ctx, scan, '\r');
		break;
	    case 't':
		string_add (ctx, scan, '\t');
		break;
	    case 'b':
		string_add (ctx, scan, '\b');
		break;
	    case 'f':
		string_add (ctx, scan, '\f');
		break;
	    case '\\':
		string_add (ctx, scan, '\\');
		break;
	    case '(':
		string_add (ctx, scan, '(');
		break;
	    case ')':
		string_add (ctx, scan, ')');
		break;

	    case '0': case '1': case '2': case '3':
	    case '4': case '5': case '6': case '7':
		{ /* octal code: \d{1,3} */
		    int i;

		    c = next - '0';

		    for (i = 0; i < 2; i++) {
			next = csi_file_getc (src);
			switch (next) {
			case EOF:
			    return;

			case '0': case '1': case '2': case '3':
			case '4': case '5': case '6': case '7':
			    c = 8*c + next-'0';
			    break;

			default:
			    csi_file_putc (src, next);
			    goto octal_code_done;
			}
		    }
  octal_code_done:
		    string_add (ctx, scan, c);
		}
		break;

	    case 0xa:
		/* skip the newline */
		next = csi_file_getc (src); /* might be compound LFCR */
		switch (next) {
		case EOF:
		    return;
		case 0xc:
		    break;
		default:
		    csi_file_putc (src, next);
		    break;
		}
		scan->line_number++;
		break;
	    case 0xc:
		break;

	    default:
		/* ignore the '\' */
		break;
	    }
	    break;

	case '(':
	    string_p++;
	    string_add (ctx, scan, c);
	    break;

	case ')':
	    if (--string_p == 0) {
		string_end (ctx, scan);
		goto scan_none;
	    }
	    /* fall through */
	default:
	    string_add (ctx, scan, c);
	    break;
	}
    }
    longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT));

scan_hex:
    buffer_reset (&scan->buffer);
    scan->accumulator_count = 0;
    scan->accumulator = 0;
    while ((c = csi_file_getc (src)) != EOF) {
	switch (c) {
	case 0xa:
	    scan->line_number++;
	case 0x0:
	case 0x9:
	case 0xc:
	case 0xd:
	case 0x20: /* ignore whitespace */
	    break;

	case '>':
	    hex_end (ctx, scan); /* fixup odd digit with '0' */
	    goto scan_none;

	case '0':
	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	case '8':
	case '9':
	case 'a':
	case 'b':
	case 'c':
	case 'd':
	case 'e':
	case 'f':
	case 'A':
	case 'B':
	case 'C':
	case 'D':
	case 'E':
	case 'F':
	    hex_add (ctx, scan, c);
	    break;

	default:
	    longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT));
	}
    }
    longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT));

scan_base85:
    buffer_reset (&scan->buffer);
    scan->accumulator = 0;
    scan->accumulator_count = 0;
    while ((c = csi_file_getc (src)) != EOF) {
	switch (c) {
	case '~':
	    next = csi_file_getc (src);
	    switch (next) {
	    case EOF:
		return;

	    case '>':
		base85_end (ctx, scan, deflate);
		deflate = 0;
		goto scan_none;
	    }
	    csi_file_putc (src, next);

	    /* fall-through */
	default:
	    base85_add (ctx, scan, c);
	    break;
	}
    }
    longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT));

scan_base64:
    buffer_reset (&scan->buffer);
    scan->accumulator = 0;
    scan->accumulator_count = 0;
    while ((c = csi_file_getc (src)) != EOF) {
	switch (c) {
	case '}':
	    next = csi_file_getc (src);
	    switch (next) {
	    case EOF:
		return;

	    case '>':
		base64_end (ctx, scan);
		goto scan_none;
	    }
	    longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT));

	default:
	    base64_add (ctx, scan, c);
	    break;
	}
    }
    longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT));
}

static csi_status_t
_scan_push (csi_t *ctx, csi_object_t *obj)
{
    if (DEBUG_SCAN) {
	fprintf (stderr, "push ");
	fprintf_obj (stderr, ctx, obj);
    }
    return _csi_push_ostack (ctx, obj);
}

static csi_status_t
_scan_execute (csi_t *ctx, csi_object_t *obj)
{
    if (DEBUG_SCAN) {
	fprintf (stderr, "exec ");
	fprintf_obj (stderr, ctx, obj);
    }
    return csi_object_execute (ctx, obj);
}

csi_status_t
_csi_scanner_init (csi_t *ctx, csi_scanner_t *scanner)
{
    csi_status_t status;

    memset (scanner, 0, sizeof (csi_scanner_t));

    status = buffer_init (ctx, &scanner->buffer);
    if (status)
	return status;

    status = _csi_stack_init (ctx, &scanner->procedure_stack, 4);
    if (status)
	return status;

    scanner->bind = 0;
    scanner->push = _scan_push;
    scanner->execute = _scan_execute;

    return CSI_STATUS_SUCCESS;
}

void
_csi_scanner_fini (csi_t *ctx, csi_scanner_t *scanner)
{
    buffer_fini (ctx, &scanner->buffer);
    _csi_stack_fini (ctx, &scanner->procedure_stack);
    if (scanner->build_procedure.type != CSI_OBJECT_TYPE_NULL)
	csi_object_free (ctx, &scanner->build_procedure);
}

csi_status_t
_csi_scan_file (csi_t *ctx, csi_file_t *src)
{
    csi_status_t status;
    int old_line_number;

    /* This function needs to be reentrant to handle recursive scanners.
     * i.e. one script executes a second.
     */

    if (ctx->scanner.depth++ == 0) {
	if ((status = setjmp (ctx->scanner.jmpbuf))) {
	    ctx->scanner.depth = 0;
	    return status;
	}
    }

    old_line_number = ctx->scanner.line_number;
    ctx->scanner.line_number = 0;

    _scan_file (ctx, src);

    ctx->scanner.line_number = old_line_number;

    --ctx->scanner.depth;
    return CSI_STATUS_SUCCESS;
}

struct _translate_closure {
    csi_dictionary_t *opcodes;
    cairo_write_func_t write_func;
    void *closure;
};

static csi_status_t
_translate_name (csi_t *ctx,
	         csi_name_t name,
		 csi_boolean_t executable,
		 struct _translate_closure *closure)
{
    if (executable) {
	csi_dictionary_entry_t *entry;
	uint16_t u16;

	/* Bind executable names.
	 * XXX This may break some scripts that overload system operators.
	 */
	entry = _csi_hash_table_lookup (&closure->opcodes->hash_table,
		                        (csi_hash_entry_t *) &name);
	if (entry == NULL)
	    goto STRING;

	u16 = entry->value.datum.integer;
	u16 = be16 (u16);
	closure->write_func (closure->closure, (unsigned char *) &u16, 2);
    } else {
	closure->write_func (closure->closure, (unsigned char *) "/", 1);
STRING:
	closure->write_func (closure->closure,
		             (unsigned char *) name,
			     strlen ((char *) name));
	closure->write_func (closure->closure, (unsigned char *) "\n", 1);
    }

    return CSI_STATUS_SUCCESS;
}

static csi_status_t
_translate_operator (csi_t *ctx,
	             csi_operator_t op,
		     csi_boolean_t executable,
		     struct _translate_closure *closure)
{
    csi_dictionary_entry_t *entry;
    uint16_t u16;

    entry = _csi_hash_table_lookup (&closure->opcodes->hash_table,
	                            (csi_hash_entry_t *) &op);
    if (entry == NULL)
	return _csi_error (CSI_STATUS_INVALID_SCRIPT);

    u16 = entry->value.datum.integer;
    if (! executable)
	u16 += 1 << 8;
    u16 = be16 (u16);
    closure->write_func (closure->closure, (unsigned char *) &u16, 2);

    return CSI_STATUS_SUCCESS;
}

static csi_status_t
_translate_integer (csi_t *ctx,
	            csi_integer_t i,
		    struct _translate_closure *closure)
{
    uint8_t hdr;
    union {
	int8_t i8;
	uint8_t u8;
	int16_t i16;
	uint16_t u16;
	int32_t i32;
	uint32_t u32;
    } u;
    int len;

#if WORDS_BIGENDIAN
    if (i < INT16_MIN) {
	hdr = MSB_INT32;
	len = 4;
	u.i32 = i;
    } else if (i < INT8_MIN) {
	hdr = MSB_INT16;
	len = 2;
	u.i16 = i;
    } else if (i < 0) {
	hdr = MSB_INT8;
	len = 1;
	u.i8 = i;
    } else if (i <= UINT8_MAX) {
	hdr = MSB_UINT8;
	len = 1;
	u.u8 = i;
    } else if (i <= UINT16_MAX) {
	hdr = MSB_UINT16;
	len = 2;
	u.u16 = i;
    } else {
	hdr = MSB_INT32;
	len = 4;
	u.u32 = i;
    }
#else
    if (i < INT16_MIN) {
	hdr = LSB_INT32;
	len = 4;
	u.i32 = i;
    } else if (i < INT8_MIN) {
	hdr = LSB_INT16;
	len = 2;
	u.i16 = i;
    } else if (i < 0) {
	hdr = LSB_INT8;
	len = 1;
	u.i8 = i;
    } else if (i <= UINT8_MAX) {
	hdr = LSB_UINT8;
	len = 1;
	u.u8 = i;
    } else if (i <= UINT16_MAX) {
	hdr = LSB_UINT16;
	len = 2;
	u.u16 = i;
    } else {
	hdr = LSB_INT32;
	len = 4;
	u.u32 = i;
    }
#endif

    closure->write_func (closure->closure, (unsigned char *) &hdr, 1);
    closure->write_func (closure->closure, (unsigned char *) &u, len);

    return CSI_STATUS_SUCCESS;
}

static csi_status_t
_translate_real (csi_t *ctx,
	         csi_real_t real,
		 struct _translate_closure *closure)
{
    uint8_t hdr;

    if (real >= INT32_MIN && real <= INT32_MAX && (int) real == real)
	return _translate_integer (ctx, real, closure);

#if WORDS_BIGENDIAN
    hdr = MSB_FLOAT32;
#else
    hdr = LSB_FLOAT32;
#endif
    closure->write_func (closure->closure, (unsigned char *) &hdr, 1);
    closure->write_func (closure->closure, (unsigned char *) &real, 4);

    return CSI_STATUS_SUCCESS;
}

static csi_status_t
_translate_string (csi_t *ctx,
	           csi_string_t *string,
		   struct _translate_closure *closure)
{
    uint8_t hdr;
    union {
	uint8_t u8;
	uint16_t u16;
	uint32_t u32;
    } u;
    void *buf;
    unsigned long hdr_len, buf_len, deflate;
    int method;

    buf = string->string;
    buf_len = string->len;
    deflate = string->deflate;
    method = string->method;

#if HAVE_LZO
    if (method == NONE && buf_len > 16) {
	unsigned long mem_len = 2*string->len > LZO2A_999_MEM_COMPRESS ? 2*string->len : LZO2A_999_MEM_COMPRESS;
	void *mem = malloc (mem_len);
	void *work = malloc(LZO2A_999_MEM_COMPRESS);

	if (lzo2a_999_compress ((lzo_bytep) buf, buf_len,
				(lzo_bytep) mem, &mem_len,
				work) == 0 &&
	    8+2*mem_len < buf_len)
	{
	    method = LZO;
	    deflate = buf_len;
	    buf_len = mem_len;
	    buf = mem;
	}
	else
	{
	    free (mem);
	}

	free (work);
    }
#if HAVE_ZLIB
    if (method == ZLIB) {
	buf_len = string->deflate;
	buf = malloc (string->deflate);
	if (uncompress ((Bytef *) buf, &buf_len,
			(Bytef *) string->string, string->len) == Z_OK)
	{
	    if (buf_len <= 8 + 2*string->len) {
		method = NONE;
		deflate = 0;
	    } else {
		unsigned long mem_len = 2*string->deflate;
		void *mem = malloc (mem_len);
		void *work = malloc(LZO2A_999_MEM_COMPRESS);

		if (lzo2a_999_compress ((lzo_bytep) buf, buf_len,
					(lzo_bytep) mem, &mem_len,
					work) == 0)
		{
		    if (8 + mem_len > buf_len) {
			method = NONE;
			deflate = 0;
		    } else {
			free (buf);
			method = LZO;
			deflate = buf_len;
			buf_len = mem_len;
			buf = mem;
			assert(deflate);
		    }
		}
		else
		{
		    free (buf);
		    buf = string->string;
		    buf_len = string->len;
		}

		free (work);
	    }
	}
	else
	{
	    free (buf);
	    buf = string->string;
	    buf_len = string->len;
	}
    }
#endif
#endif

    if (method == LZO) {
	hdr = STRING_LZO;
	u.u32 = to_be32 (buf_len);
	hdr_len = 4;
    } else {
#if WORDS_BIGENDIAN
	if (buf_len <= UINT8_MAX) {
	    hdr = STRING_1;
	    u.u8 = buf_len;
	    hdr_len = 1;
	} else if (buf_len <= UINT16_MAX) {
	    hdr = STRING_2_MSB;
	    u.u16 = buf_len;
	    hdr_len = 2;
	} else {
	    hdr = STRING_4_MSB;
	    u.u32 = buf_len;
	    hdr_len = 4;
	}
#else
	if (buf_len <= UINT8_MAX) {
	    hdr = STRING_1;
	    u.u8 = buf_len;
	    hdr_len = 1;
	} else if (buf_len <= UINT16_MAX) {
	    hdr = STRING_2_LSB;
	    u.u16 = buf_len;
	    hdr_len = 2;
	} else {
	    hdr = STRING_4_LSB;
	    u.u32 = buf_len;
	    hdr_len = 4;
	}
#endif
	if (deflate) {
	    assert (method == ZLIB);
	    hdr |= STRING_DEFLATE;
	}
    }

    closure->write_func (closure->closure, (unsigned char *) &hdr, 1);
    closure->write_func (closure->closure, (unsigned char *) &u, hdr_len);
    if (deflate) {
	uint32_t u32 = to_be32 (deflate);
	closure->write_func (closure->closure, (unsigned char *) &u32, 4);
    }
    closure->write_func (closure->closure, (unsigned char *) buf, buf_len);

    if (buf != string->string)
	free (buf);

    return CSI_STATUS_SUCCESS;
}

static csi_status_t
_translate_push (csi_t *ctx, csi_object_t *obj)
{
    struct _translate_closure *closure = ctx->scanner.closure;

    if (0) {
	fprintf (stderr, "push ");
	fprintf_obj (stderr, ctx, obj);
    }

    switch (csi_object_get_type (obj)) {
    case CSI_OBJECT_TYPE_NAME:
	return _translate_name (ctx, obj->datum.name, FALSE, closure);

    case CSI_OBJECT_TYPE_OPERATOR:
	return _translate_operator (ctx, obj->datum.op, FALSE, closure);

    case CSI_OBJECT_TYPE_INTEGER:
	return _translate_integer (ctx, obj->datum.integer, closure);

    case CSI_OBJECT_TYPE_REAL:
	return _translate_real (ctx, obj->datum.real, closure);

    case CSI_OBJECT_TYPE_STRING:
	return _translate_string (ctx, obj->datum.string, closure);

    case CSI_OBJECT_TYPE_NULL:
    case CSI_OBJECT_TYPE_BOOLEAN:
    case CSI_OBJECT_TYPE_MARK:
    case CSI_OBJECT_TYPE_ARRAY:
    case CSI_OBJECT_TYPE_DICTIONARY:
    case CSI_OBJECT_TYPE_FILE:
    case CSI_OBJECT_TYPE_MATRIX:
    case CSI_OBJECT_TYPE_CONTEXT:
    case CSI_OBJECT_TYPE_FONT:
    case CSI_OBJECT_TYPE_PATTERN:
    case CSI_OBJECT_TYPE_SCALED_FONT:
    case CSI_OBJECT_TYPE_SURFACE:
	longjmp (ctx->scanner.jmpbuf,  _csi_error (CSI_STATUS_INVALID_SCRIPT));
	break;
    }

    csi_object_free (ctx, obj);
    return CSI_STATUS_SUCCESS;
}

static csi_status_t
_translate_execute (csi_t *ctx, csi_object_t *obj)
{
    struct _translate_closure *closure = ctx->scanner.closure;

    if (0) {
	fprintf (stderr, "exec ");
	fprintf_obj (stderr, ctx, obj);
    }

    switch (csi_object_get_type (obj)) {
    case CSI_OBJECT_TYPE_NAME:
	return _translate_name (ctx, obj->datum.name, TRUE, closure);

    case CSI_OBJECT_TYPE_OPERATOR:
	return _translate_operator (ctx, obj->datum.op, TRUE, closure);

    case CSI_OBJECT_TYPE_INTEGER:
	return _translate_integer (ctx, obj->datum.integer, closure);

    case CSI_OBJECT_TYPE_REAL:
	return _translate_real (ctx, obj->datum.real, closure);

    case CSI_OBJECT_TYPE_STRING:
	return _translate_string (ctx, obj->datum.string, closure);

    case CSI_OBJECT_TYPE_NULL:
    case CSI_OBJECT_TYPE_BOOLEAN:
    case CSI_OBJECT_TYPE_MARK:
    case CSI_OBJECT_TYPE_ARRAY:
    case CSI_OBJECT_TYPE_DICTIONARY:
    case CSI_OBJECT_TYPE_FILE:
    case CSI_OBJECT_TYPE_MATRIX:
    case CSI_OBJECT_TYPE_CONTEXT:
    case CSI_OBJECT_TYPE_FONT:
    case CSI_OBJECT_TYPE_PATTERN:
    case CSI_OBJECT_TYPE_SCALED_FONT:
    case CSI_OBJECT_TYPE_SURFACE:
	longjmp (ctx->scanner.jmpbuf,  _csi_error (CSI_STATUS_INVALID_SCRIPT));
	break;
    }

    return CSI_STATUS_SUCCESS;
}

static csi_status_t
build_opcodes (csi_t *ctx, csi_dictionary_t **out)
{
    csi_object_t obj;
    csi_dictionary_t *dict;
    const csi_operator_def_t *def;
    csi_status_t status;
    int opcode = OPCODE << 8;

    status = csi_dictionary_new (ctx, &obj);
    if (_csi_unlikely (status))
	return status;

    dict = obj.datum.dictionary;

    csi_integer_new (&obj, opcode++);
    status = csi_dictionary_put (ctx, dict, 0, &obj);
    if (_csi_unlikely (status))
	goto FAIL;

    for (def = _csi_operators (); def->name != NULL; def++) {
	csi_object_t name;
	csi_dictionary_entry_t *entry;
	int code;

	entry = _csi_hash_table_lookup (&dict->hash_table,
		                        (csi_hash_entry_t *) &def->op);
	if (entry == NULL) {
	    code = opcode++;
	    csi_integer_new (&obj, code);
	    status = csi_dictionary_put (ctx, dict, (csi_name_t) def->op, &obj);
	    if (_csi_unlikely (status))
		goto FAIL;
	} else {
	    code = entry->value.datum.integer;
	    csi_integer_new (&obj, code);
	}
	assert (ctx->opcode[code & 0xff] == def->op);

	status = csi_name_new_static (ctx, &name, def->name);
	if (_csi_unlikely (status))
	    goto FAIL;

	status = csi_dictionary_put (ctx, dict, name.datum.name, &obj);
	if (_csi_unlikely (status))
	    goto FAIL;
    }

    *out = dict;
    return CSI_STATUS_SUCCESS;

FAIL:
    csi_dictionary_free (ctx, dict);
    return status;
}

csi_status_t
_csi_translate_file (csi_t *ctx,
	             csi_file_t *file,
		     cairo_write_func_t write_func,
		     void *closure)
{
    csi_status_t status;
    struct _translate_closure translator;

    if ((status = setjmp (ctx->scanner.jmpbuf)))
	return status;

    status = build_opcodes (ctx, &translator.opcodes);
    if (_csi_unlikely (status))
	return status;

    translator.write_func = write_func;
    translator.closure = closure;
    ctx->scanner.closure = &translator;

    ctx->scanner.bind = 1;
    ctx->scanner.push = _translate_push;
    ctx->scanner.execute = _translate_execute;

    _scan_file (ctx, file);

    ctx->scanner.bind = 0;
    ctx->scanner.push = _scan_push;
    ctx->scanner.execute = _scan_execute;

    csi_dictionary_free (ctx, translator.opcodes);

    return CSI_STATUS_SUCCESS;
}
