blob: dfce8a1adf661041cbe9caff4a3921436afa1db4 [file] [log] [blame]
/*
* 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 "config.h"
#include "cairo-script-private.h"
#include "cairo.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#ifndef MAX
#define MAX(a,b) (((a)>=(b))?(a):(b))
#endif
csi_status_t
_csi_error (csi_status_t status)
{
return status;
}
/* XXX track global/local memory, cap etc, mark/sweep GC */
void *
_csi_alloc (csi_t *ctx, int size)
{
return malloc (size);
}
void *
_csi_alloc0 (csi_t *ctx, int size)
{
void *ptr;
ptr = _csi_alloc (ctx, size);
if (_csi_likely (ptr != NULL))
memset (ptr, 0, size);
return ptr;
}
void *
_csi_realloc (csi_t *ctx, void *ptr, int size)
{
return realloc (ptr, size);
}
void
_csi_free (csi_t *ctx, void *ptr)
{
if (_csi_unlikely (ptr == NULL))
return;
free (ptr);
}
void *
_csi_perm_alloc (csi_t *ctx, int size)
{
csi_chunk_t *chunk;
void *ptr;
size = (size + sizeof (void *)-1) & -sizeof (void *);
chunk = ctx->perm_chunk;
if (chunk == NULL || chunk->rem < size) {
int chunk_size = (size + 8191) & -8192;
chunk = _csi_alloc (ctx, sizeof (csi_chunk_t) + chunk_size);
if (_csi_unlikely (chunk == NULL))
return NULL;
chunk->rem = chunk_size;
chunk->ptr = (char *) (chunk + 1);
chunk->next = ctx->perm_chunk;
ctx->perm_chunk = chunk;
}
ptr = chunk->ptr;
chunk->ptr += size;
chunk->rem -= size;
return ptr;
}
void *
_csi_slab_alloc (csi_t *ctx, int size)
{
#if CSI_DEBUG_MALLOC
return malloc (size);
#else
int chunk_size;
csi_chunk_t *chunk;
void *ptr;
chunk_size = 2 * sizeof (void *);
chunk_size = (size + chunk_size - 1) / chunk_size;
if (ctx->slabs[chunk_size].free_list) {
ptr = ctx->slabs[chunk_size].free_list;
ctx->slabs[chunk_size].free_list = *(void **) ptr;
return ptr;
}
chunk = ctx->slabs[chunk_size].chunk;
if (chunk == NULL || ! chunk->rem) {
int cnt = MAX (128, 8192 / (chunk_size * 2 * sizeof (void *)));
chunk = _csi_alloc (ctx,
sizeof (csi_chunk_t) +
cnt * chunk_size * 2 * sizeof (void *));
if (_csi_unlikely (chunk == NULL))
return NULL;
chunk->rem = cnt;
chunk->ptr = (char *) (chunk + 1);
chunk->next = ctx->slabs[chunk_size].chunk;
ctx->slabs[chunk_size].chunk = chunk;
}
ptr = chunk->ptr;
chunk->ptr += chunk_size * 2 * sizeof (void *);
chunk->rem--;
return ptr;
#endif
}
void
_csi_slab_free (csi_t *ctx, void *ptr, int size)
{
int chunk_size;
void **free_list;
if (_csi_unlikely (ptr == NULL))
return;
#if CSI_DEBUG_MALLOC
free (ptr);
#else
chunk_size = 2 * sizeof (void *);
chunk_size = (size + chunk_size - 1) / chunk_size;
free_list = ptr;
*free_list = ctx->slabs[chunk_size].free_list;
ctx->slabs[chunk_size].free_list = ptr;
#endif
}
static void
_csi_perm_fini (csi_t *ctx)
{
while (ctx->perm_chunk != NULL) {
csi_chunk_t *chunk = ctx->perm_chunk;
ctx->perm_chunk = chunk->next;
_csi_free (ctx, chunk);
}
}
static void
_csi_slab_fini (csi_t *ctx)
{
unsigned int i;
for (i = 0; i < sizeof (ctx->slabs) / sizeof (ctx->slabs[0]); i++) {
while (ctx->slabs[i].chunk != NULL) {
csi_chunk_t *chunk = ctx->slabs[i].chunk;
ctx->slabs[i].chunk = chunk->next;
_csi_free (ctx, chunk);
}
}
}
static csi_status_t
_add_operator (csi_t *ctx,
csi_dictionary_t *dict,
const csi_operator_def_t *def)
{
csi_object_t name;
csi_object_t operator;
csi_status_t status;
status = csi_name_new_static (ctx, &name, def->name);
if (status)
return status;
csi_operator_new (&operator, def->op);
return csi_dictionary_put (ctx, dict, name.datum.name, &operator);
}
static csi_status_t
_add_integer_constant (csi_t *ctx,
csi_dictionary_t *dict,
const csi_integer_constant_def_t *def)
{
csi_object_t name;
csi_object_t constant;
csi_status_t status;
status = csi_name_new_static (ctx, &name, def->name);
if (status)
return status;
csi_integer_new (&constant, def->value);
return csi_dictionary_put (ctx, dict, name.datum.name, &constant);
}
static csi_status_t
_add_real_constant (csi_t *ctx,
csi_dictionary_t *dict,
const csi_real_constant_def_t *def)
{
csi_object_t name;
csi_object_t constant;
csi_status_t status;
status = csi_name_new_static (ctx, &name, def->name);
if (status)
return status;
csi_real_new (&constant, def->value);
return csi_dictionary_put (ctx, dict, name.datum.name, &constant);
}
static csi_status_t
_init_dictionaries (csi_t *ctx)
{
csi_status_t status;
csi_stack_t *stack;
csi_object_t obj;
csi_dictionary_t *dict, *opcodes;
const csi_operator_def_t *odef;
const csi_integer_constant_def_t *idef;
const csi_real_constant_def_t *rdef;
unsigned n;
stack = &ctx->dstack;
status = _csi_stack_init (ctx, stack, 4);
if (_csi_unlikely (status))
return status;
/* systemdict */
status = csi_dictionary_new (ctx, &obj);
if (_csi_unlikely (status))
return status;
status = _csi_stack_push (ctx, stack, &obj);
if (_csi_unlikely (status))
return status;
dict = obj.datum.dictionary;
status = csi_dictionary_new (ctx, &obj);
if (_csi_unlikely (status))
return status;
opcodes = obj.datum.dictionary;
n = 0;
csi_integer_new (&obj, n);
status = csi_dictionary_put (ctx, opcodes, 0, &obj);
if (_csi_unlikely (status))
return status;
ctx->opcode[n++] = NULL;
/* fill systemdict with operators */
for (odef = _csi_operators (); odef->name != NULL; odef++) {
status = _add_operator (ctx, dict, odef);
if (_csi_unlikely (status))
return status;
if (! csi_dictionary_has (opcodes, (csi_name_t) odef->op)) {
csi_integer_new (&obj, n);
status = csi_dictionary_put (ctx,
opcodes, (csi_name_t) odef->op, &obj);
if (_csi_unlikely (status))
return status;
assert (n < sizeof (ctx->opcode) / sizeof (ctx->opcode[0]));
ctx->opcode[n++] = odef->op;
}
}
csi_dictionary_free (ctx, opcodes);
/* add constants */
for (idef = _csi_integer_constants (); idef->name != NULL; idef++) {
status = _add_integer_constant (ctx, dict, idef);
if (_csi_unlikely (status))
return status;
}
for (rdef = _csi_real_constants (); rdef->name != NULL; rdef++) {
status = _add_real_constant (ctx, dict, rdef);
if (_csi_unlikely (status))
return status;
}
/* and seal */
//dict.type &= ~CSI_OBJECT_ATTR_WRITABLE;
/* globaldict */
status = csi_dictionary_new (ctx, &obj);
if (_csi_unlikely (status))
return status;
status = _csi_stack_push (ctx, stack, &obj);
if (_csi_unlikely (status))
return status;
/* userdict */
status = csi_dictionary_new (ctx, &obj);
if (_csi_unlikely (status))
return status;
status = _csi_stack_push (ctx, stack, &obj);
if (_csi_unlikely (status))
return status;
return CSI_STATUS_SUCCESS;
}
/* intern string */
typedef struct _cairo_intern_string {
csi_hash_entry_t hash_entry;
int len;
char *string;
} csi_intern_string_t;
static unsigned long
_intern_string_hash (const char *str, int len)
{
const signed char *p = (const signed char *) str;
if (len > 0) {
unsigned int h = *p;
while (--len)
h = (h << 5) - h + *++p;
return h;
}
return 0;
}
static cairo_bool_t
_intern_string_equal (const void *_a, const void *_b)
{
const csi_intern_string_t *a = _a;
const csi_intern_string_t *b = _b;
if (a->len != b->len)
return FALSE;
return memcmp (a->string, b->string, a->len) == 0;
}
static void
_csi_init (csi_t *ctx)
{
csi_status_t status;
memset (ctx, 0, sizeof (*ctx));
ctx->status = CSI_STATUS_SUCCESS;
ctx->ref_count = 1;
ctx->scanner.line_number = -1;
status = _csi_hash_table_init (&ctx->strings, _intern_string_equal);
if (status)
goto FAIL;
status = _csi_stack_init (ctx, &ctx->ostack, 2048);
if (status)
goto FAIL;
status = _init_dictionaries (ctx);
if (status)
goto FAIL;
status = _csi_scanner_init (ctx, &ctx->scanner);
if (status)
goto FAIL;
return;
FAIL:
if (ctx->status == CSI_STATUS_SUCCESS)
ctx->status = status;
}
static void
_csi_finish (csi_t *ctx)
{
_csi_stack_fini (ctx, &ctx->ostack);
_csi_stack_fini (ctx, &ctx->dstack);
_csi_scanner_fini (ctx, &ctx->scanner);
_csi_hash_table_fini (&ctx->strings);
}
csi_status_t
_csi_name_define (csi_t *ctx, csi_name_t name, csi_object_t *obj)
{
return csi_dictionary_put (ctx,
ctx->dstack.objects[ctx->dstack.len-1].datum.dictionary,
name,
obj);
}
csi_status_t
_csi_name_lookup (csi_t *ctx, csi_name_t name, csi_object_t *obj)
{
int i;
for (i = ctx->dstack.len; i--; ) {
csi_dictionary_t *dict;
csi_dictionary_entry_t *entry;
dict = ctx->dstack.objects[i].datum.dictionary;
entry = _csi_hash_table_lookup (&dict->hash_table,
(csi_hash_entry_t *) &name);
if (entry != NULL) {
*obj = entry->value;
return CSI_STATUS_SUCCESS;
}
}
return _csi_error (CSI_STATUS_INVALID_SCRIPT);
}
csi_status_t
_csi_name_undefine (csi_t *ctx, csi_name_t name)
{
unsigned int i;
for (i = ctx->dstack.len; --i; ) {
if (csi_dictionary_has (ctx->dstack.objects[i].datum.dictionary,
name))
{
csi_dictionary_remove (ctx,
ctx->dstack.objects[i].datum.dictionary,
name);
return CSI_STATUS_SUCCESS;
}
}
return _csi_error (CSI_STATUS_INVALID_SCRIPT);
}
csi_status_t
_csi_intern_string (csi_t *ctx, const char **str_inout, int len)
{
char *str = (char *) *str_inout;
csi_intern_string_t tmpl, *istring;
csi_status_t status = CSI_STATUS_SUCCESS;
tmpl.hash_entry.hash = _intern_string_hash (str, len);
tmpl.len = len;
tmpl.string = (char *) str;
istring = _csi_hash_table_lookup (&ctx->strings, &tmpl.hash_entry);
if (istring == NULL) {
istring = _csi_perm_alloc (ctx,
sizeof (csi_intern_string_t) + len + 1);
if (istring != NULL) {
istring->hash_entry.hash = tmpl.hash_entry.hash;
istring->len = tmpl.len;
istring->string = (char *) (istring + 1);
memcpy (istring->string, str, len);
istring->string[len] = '\0';
status = _csi_hash_table_insert (&ctx->strings,
&istring->hash_entry);
if (_csi_unlikely (status)) {
_csi_free (ctx, istring);
return status;
}
} else
return _csi_error (CSI_STATUS_NO_MEMORY);
}
*str_inout = istring->string;
return CSI_STATUS_SUCCESS;
}
/* Public */
static csi_t _csi_nil = { -1, CSI_STATUS_NO_MEMORY };
csi_t *
cairo_script_interpreter_create (void)
{
csi_t *ctx;
ctx = malloc (sizeof (csi_t));
if (ctx == NULL)
return (csi_t *) &_csi_nil;
_csi_init (ctx);
return ctx;
}
void
cairo_script_interpreter_install_hooks (csi_t *ctx,
const csi_hooks_t *hooks)
{
if (ctx->status)
return;
ctx->hooks = *hooks;
}
cairo_status_t
cairo_script_interpreter_run (csi_t *ctx, const char *filename)
{
csi_object_t file;
if (ctx->status)
return ctx->status;
if (ctx->finished)
return ctx->status = CSI_STATUS_INTERPRETER_FINISHED;
ctx->status = csi_file_new (ctx, &file, filename, "r");
if (ctx->status)
return ctx->status;
file.type |= CSI_OBJECT_ATTR_EXECUTABLE;
ctx->status = csi_object_execute (ctx, &file);
csi_object_free (ctx, &file);
return ctx->status;
}
cairo_status_t
cairo_script_interpreter_feed_stream (csi_t *ctx, FILE *stream)
{
csi_object_t file;
if (ctx->status)
return ctx->status;
if (ctx->finished)
return ctx->status = CSI_STATUS_INTERPRETER_FINISHED;
ctx->status = csi_file_new_for_stream (ctx, &file, stream);
if (ctx->status)
return ctx->status;
file.type |= CSI_OBJECT_ATTR_EXECUTABLE;
ctx->status = csi_object_execute (ctx, &file);
csi_object_free (ctx, &file);
return ctx->status;
}
cairo_status_t
cairo_script_interpreter_feed_string (csi_t *ctx, const char *line, int len)
{
csi_object_t file;
if (ctx->status)
return ctx->status;
if (ctx->finished)
return ctx->status = CSI_STATUS_INTERPRETER_FINISHED;
if (len < 0)
len = strlen (line);
ctx->status = csi_file_new_for_bytes (ctx, &file, line, len);
if (ctx->status)
return ctx->status;
file.type |= CSI_OBJECT_ATTR_EXECUTABLE;
ctx->status = csi_object_execute (ctx, &file);
csi_object_free (ctx, &file);
return ctx->status;
}
unsigned int
cairo_script_interpreter_get_line_number (csi_t *ctx)
{
return ctx->scanner.line_number + 1; /* 1 index based */
}
csi_t *
cairo_script_interpreter_reference (csi_t *ctx)
{
ctx->ref_count++;
return ctx;
}
slim_hidden_def (cairo_script_interpreter_reference);
cairo_status_t
cairo_script_interpreter_finish (csi_t *ctx)
{
csi_status_t status;
status = ctx->status;
if (! ctx->finished) {
_csi_finish (ctx);
ctx->finished = 1;
} else if (status == CSI_STATUS_SUCCESS) {
status = ctx->status = CSI_STATUS_INTERPRETER_FINISHED;
}
return status;
}
static void
_csi_fini (csi_t *ctx)
{
if (! ctx->finished)
_csi_finish (ctx);
if (ctx->free_array != NULL)
csi_array_free (ctx, ctx->free_array);
if (ctx->free_dictionary != NULL)
csi_dictionary_free (ctx, ctx->free_dictionary);
if (ctx->free_string != NULL)
csi_string_free (ctx, ctx->free_string);
_csi_slab_fini (ctx);
_csi_perm_fini (ctx);
}
cairo_status_t
cairo_script_interpreter_destroy (csi_t *ctx)
{
csi_status_t status;
status = ctx->status;
if (--ctx->ref_count)
return status;
_csi_fini (ctx);
free (ctx);
return status;
}
slim_hidden_def (cairo_script_interpreter_destroy);
cairo_status_t
cairo_script_interpreter_translate_stream (FILE *stream,
cairo_write_func_t write_func,
void *closure)
{
csi_t ctx;
csi_object_t src;
csi_status_t status;
_csi_init (&ctx);
status = csi_file_new_for_stream (&ctx, &src, stream);
if (status)
goto BAIL;
status = _csi_translate_file (&ctx, src.datum.file, write_func, closure);
BAIL:
csi_object_free (&ctx, &src);
_csi_fini (&ctx);
return status;
}