1995-08-09 08:06:35 -07:00
|
|
|
/***********************************************************************/
|
|
|
|
/* */
|
1996-04-30 07:53:58 -07:00
|
|
|
/* Objective Caml */
|
1995-08-09 08:06:35 -07:00
|
|
|
/* */
|
|
|
|
/* Xavier Leroy, projet Cristal, INRIA Rocquencourt */
|
|
|
|
/* */
|
1996-04-30 07:53:58 -07:00
|
|
|
/* Copyright 1996 Institut National de Recherche en Informatique et */
|
1995-08-09 08:06:35 -07:00
|
|
|
/* Automatique. Distributed only by permission. */
|
|
|
|
/* */
|
|
|
|
/***********************************************************************/
|
|
|
|
|
|
|
|
/* $Id$ */
|
|
|
|
|
1995-05-04 03:15:53 -07:00
|
|
|
/* Structured output */
|
|
|
|
|
1996-05-28 05:41:37 -07:00
|
|
|
#include <string.h>
|
1996-04-01 07:24:38 -08:00
|
|
|
#include "alloc.h"
|
1995-05-04 03:15:53 -07:00
|
|
|
#include "fail.h"
|
|
|
|
#include "gc.h"
|
|
|
|
#include "intext.h"
|
|
|
|
#include "io.h"
|
|
|
|
#include "memory.h"
|
|
|
|
#include "misc.h"
|
|
|
|
#include "mlvalues.h"
|
|
|
|
#include "reverse.h"
|
|
|
|
#include "str.h"
|
|
|
|
|
|
|
|
/* To keep track of sharing in externed objects */
|
|
|
|
|
|
|
|
typedef unsigned long byteoffset_t;
|
|
|
|
|
|
|
|
struct extern_obj {
|
|
|
|
byteoffset_t ofs;
|
1997-04-11 06:57:04 -07:00
|
|
|
value obj;
|
1995-05-04 03:15:53 -07:00
|
|
|
};
|
|
|
|
|
1997-04-11 06:57:04 -07:00
|
|
|
static byteoffset_t initial_ofs = 1;
|
|
|
|
static struct extern_obj * extern_table = NULL;
|
1995-11-10 04:39:48 -08:00
|
|
|
static unsigned long extern_table_size;
|
1995-05-04 03:15:53 -07:00
|
|
|
|
1996-07-01 05:43:28 -07:00
|
|
|
#ifdef ARCH_SIXTYFOUR
|
1995-11-10 04:39:48 -08:00
|
|
|
#define Hash(v) (((unsigned long) ((v) >> 3)) % extern_table_size)
|
1995-05-04 03:15:53 -07:00
|
|
|
#else
|
1995-11-10 04:39:48 -08:00
|
|
|
#define Hash(v) (((unsigned long) ((v) >> 2)) % extern_table_size)
|
1995-05-04 03:15:53 -07:00
|
|
|
#endif
|
|
|
|
|
1997-04-11 06:57:04 -07:00
|
|
|
/* Allocate a new extern table */
|
1995-05-04 03:15:53 -07:00
|
|
|
static void alloc_extern_table()
|
|
|
|
{
|
|
|
|
asize_t i;
|
|
|
|
extern_table = (struct extern_obj *)
|
1997-04-11 06:57:04 -07:00
|
|
|
stat_alloc(extern_table_size * sizeof(struct extern_obj));
|
|
|
|
for (i = 0; i < extern_table_size; i++) extern_table[i].ofs = 0;
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
|
|
|
|
1997-04-11 06:57:04 -07:00
|
|
|
/* Grow the extern table */
|
1995-05-04 03:15:53 -07:00
|
|
|
static void resize_extern_table()
|
|
|
|
{
|
|
|
|
asize_t oldsize;
|
|
|
|
struct extern_obj * oldtable;
|
1996-04-01 07:24:38 -08:00
|
|
|
value obj;
|
1997-04-11 06:57:04 -07:00
|
|
|
byteoffset_t ofs;
|
1995-05-04 03:15:53 -07:00
|
|
|
asize_t i, h;
|
|
|
|
|
|
|
|
oldsize = extern_table_size;
|
|
|
|
oldtable = extern_table;
|
|
|
|
extern_table_size = 2 * extern_table_size;
|
|
|
|
alloc_extern_table();
|
|
|
|
for (i = 0; i < oldsize; i++) {
|
1997-04-11 06:57:04 -07:00
|
|
|
ofs = oldtable[i].ofs;
|
|
|
|
if (ofs >= initial_ofs) {
|
|
|
|
obj = oldtable[i].obj;
|
1996-04-01 07:24:38 -08:00
|
|
|
h = Hash(obj);
|
1997-04-11 06:57:04 -07:00
|
|
|
while (extern_table[h].ofs >= initial_ofs) {
|
1996-04-01 07:24:38 -08:00
|
|
|
h++;
|
|
|
|
if (h >= extern_table_size) h = 0;
|
|
|
|
}
|
1997-04-11 06:57:04 -07:00
|
|
|
extern_table[h].ofs = ofs;
|
1996-04-01 07:24:38 -08:00
|
|
|
extern_table[h].obj = obj;
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
stat_free((char *) oldtable);
|
|
|
|
}
|
|
|
|
|
1997-04-11 06:57:04 -07:00
|
|
|
/* Free the extern table. We keep it around for next call if
|
|
|
|
it's still small (we did not grow it) and the initial offset
|
|
|
|
does not risk running over next time. */
|
|
|
|
static void free_extern_table()
|
|
|
|
{
|
|
|
|
if (extern_table_size > INITIAL_EXTERN_TABLE_SIZE ||
|
|
|
|
initial_ofs >= INITIAL_OFFSET_MAX) {
|
|
|
|
stat_free((char *) extern_table);
|
|
|
|
extern_table = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
1996-04-01 07:24:38 -08:00
|
|
|
/* To buffer the output */
|
1995-05-04 03:15:53 -07:00
|
|
|
|
1996-04-01 07:24:38 -08:00
|
|
|
static char * extern_block, * extern_ptr, * extern_limit;
|
|
|
|
|
|
|
|
static void alloc_extern_block()
|
|
|
|
{
|
|
|
|
extern_block = stat_alloc(INITIAL_EXTERN_BLOCK_SIZE);
|
|
|
|
extern_limit = extern_block + INITIAL_EXTERN_BLOCK_SIZE;
|
|
|
|
extern_ptr = extern_block;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void resize_extern_block(required)
|
|
|
|
int required;
|
|
|
|
{
|
|
|
|
long curr_pos, size, reqd_size;
|
|
|
|
|
|
|
|
curr_pos = extern_ptr - extern_block;
|
|
|
|
size = extern_limit - extern_block;
|
|
|
|
reqd_size = curr_pos + required;
|
|
|
|
while (size <= reqd_size) size *= 2;
|
|
|
|
extern_block = stat_resize(extern_block, size);
|
|
|
|
extern_limit = extern_block + size;
|
|
|
|
extern_ptr = extern_block + curr_pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define Write(c) \
|
|
|
|
if (extern_ptr >= extern_limit) resize_extern_block(1); \
|
|
|
|
*extern_ptr++ = (c)
|
|
|
|
|
|
|
|
/* Write integers and blocks in the output buffer */
|
|
|
|
|
|
|
|
static void writeblock(data, len)
|
|
|
|
char * data;
|
|
|
|
long len;
|
|
|
|
{
|
|
|
|
if (extern_ptr + len > extern_limit) resize_extern_block(len);
|
|
|
|
bcopy(data, extern_ptr, len);
|
|
|
|
extern_ptr += len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void writecode8(code, val)
|
1995-05-04 03:15:53 -07:00
|
|
|
int code;
|
|
|
|
long val;
|
|
|
|
{
|
1996-04-01 07:24:38 -08:00
|
|
|
if (extern_ptr + 2 > extern_limit) resize_extern_block(2);
|
|
|
|
extern_ptr[0] = code;
|
|
|
|
extern_ptr[1] = val;
|
|
|
|
extern_ptr += 2;
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
|
|
|
|
1996-04-01 07:24:38 -08:00
|
|
|
static void writecode16(code, val)
|
1995-05-04 03:15:53 -07:00
|
|
|
int code;
|
|
|
|
long val;
|
|
|
|
{
|
1996-04-01 07:24:38 -08:00
|
|
|
if (extern_ptr + 3 > extern_limit) resize_extern_block(3);
|
|
|
|
extern_ptr[0] = code;
|
|
|
|
extern_ptr[1] = val >> 8;
|
|
|
|
extern_ptr[2] = val;
|
|
|
|
extern_ptr += 3;
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
|
|
|
|
1996-04-01 07:24:38 -08:00
|
|
|
static void write32(val)
|
|
|
|
long val;
|
|
|
|
{
|
|
|
|
if (extern_ptr + 4 > extern_limit) resize_extern_block(4);
|
|
|
|
extern_ptr[0] = val >> 24;
|
|
|
|
extern_ptr[1] = val >> 16;
|
|
|
|
extern_ptr[2] = val >> 8;
|
|
|
|
extern_ptr[3] = val;
|
|
|
|
extern_ptr += 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void writecode32(code, val)
|
1995-05-04 03:15:53 -07:00
|
|
|
int code;
|
|
|
|
long val;
|
|
|
|
{
|
1996-04-01 07:24:38 -08:00
|
|
|
if (extern_ptr + 5 > extern_limit) resize_extern_block(5);
|
|
|
|
extern_ptr[0] = code;
|
|
|
|
extern_ptr[1] = val >> 24;
|
|
|
|
extern_ptr[2] = val >> 16;
|
|
|
|
extern_ptr[3] = val >> 8;
|
|
|
|
extern_ptr[4] = val;
|
|
|
|
extern_ptr += 5;
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
|
|
|
|
1996-07-01 05:43:28 -07:00
|
|
|
#ifdef ARCH_SIXTYFOUR
|
1996-04-01 07:24:38 -08:00
|
|
|
static void writecode64(code, val)
|
1995-05-04 03:15:53 -07:00
|
|
|
int code;
|
|
|
|
long val;
|
|
|
|
{
|
|
|
|
int i;
|
1996-04-01 07:24:38 -08:00
|
|
|
if (extern_ptr + 9 > extern_limit) resize_extern_block(9);
|
|
|
|
*extern_ptr ++ = code;
|
|
|
|
for (i = 64 - 8; i >= 0; i -= 8) *extern_ptr++ = val >> i;
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
1996-04-01 07:24:38 -08:00
|
|
|
/* Marshal the given value in the output buffer */
|
|
|
|
|
1995-05-04 03:15:53 -07:00
|
|
|
static byteoffset_t obj_counter; /* Number of objects emitted so far */
|
|
|
|
static unsigned long size_32; /* Size in words of 32-bit block for struct. */
|
|
|
|
static unsigned long size_64; /* Size in words of 64-bit block for struct. */
|
|
|
|
|
1997-04-11 06:57:04 -07:00
|
|
|
static void extern_invalid_argument(msg)
|
|
|
|
char * msg;
|
1996-04-01 07:24:38 -08:00
|
|
|
{
|
|
|
|
stat_free(extern_block);
|
1997-04-11 06:57:04 -07:00
|
|
|
initial_ofs += obj_counter;
|
|
|
|
free_extern_table();
|
|
|
|
invalid_argument(msg);
|
1996-04-01 07:24:38 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void extern_rec(v)
|
1995-05-04 03:15:53 -07:00
|
|
|
value v;
|
|
|
|
{
|
|
|
|
tailcall:
|
|
|
|
if (Is_long(v)) {
|
|
|
|
long n = Long_val(v);
|
|
|
|
if (n >= 0 && n < 0x40) {
|
1996-04-01 07:24:38 -08:00
|
|
|
Write(PREFIX_SMALL_INT + n);
|
1995-05-04 03:15:53 -07:00
|
|
|
} else if (n >= -(1 << 7) && n < (1 << 7)) {
|
1996-04-01 07:24:38 -08:00
|
|
|
writecode8(CODE_INT8, n);
|
1995-05-04 03:15:53 -07:00
|
|
|
} else if (n >= -(1 << 15) && n < (1 << 15)) {
|
1996-04-01 07:24:38 -08:00
|
|
|
writecode16(CODE_INT16, n);
|
1996-07-01 05:43:28 -07:00
|
|
|
#ifdef ARCH_SIXTYFOUR
|
1995-05-04 03:15:53 -07:00
|
|
|
} else if (n < -(1L << 31) || n >= (1L << 31)) {
|
1996-04-01 07:24:38 -08:00
|
|
|
writecode64(CODE_INT64, n);
|
1995-05-04 03:15:53 -07:00
|
|
|
#endif
|
|
|
|
} else
|
1996-04-01 07:24:38 -08:00
|
|
|
writecode32(CODE_INT32, n);
|
1995-12-15 02:19:01 -08:00
|
|
|
} else if (!Is_atom(v) && !Is_young(v) && !Is_in_heap(v)) {
|
1997-04-11 06:57:04 -07:00
|
|
|
extern_invalid_argument("output_value: abstract value");
|
1995-05-04 03:15:53 -07:00
|
|
|
} else {
|
|
|
|
header_t hd = Hd_val(v);
|
|
|
|
tag_t tag = Tag_hd(hd);
|
|
|
|
mlsize_t sz = Wosize_hd(hd);
|
1996-04-01 07:24:38 -08:00
|
|
|
asize_t h;
|
1995-05-04 03:15:53 -07:00
|
|
|
/* Atoms are treated specially for two reasons: they are not allocated
|
|
|
|
in the externed block, and they are automatically shared. */
|
|
|
|
if (sz == 0) {
|
|
|
|
if (tag < 16) {
|
1996-04-01 07:24:38 -08:00
|
|
|
Write(PREFIX_SMALL_BLOCK + tag);
|
1995-05-04 03:15:53 -07:00
|
|
|
} else {
|
1996-04-01 07:24:38 -08:00
|
|
|
writecode32(CODE_BLOCK32, hd);
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* Check if already seen */
|
|
|
|
if (2 * obj_counter >= extern_table_size) resize_extern_table();
|
|
|
|
h = Hash(v);
|
1997-04-11 06:57:04 -07:00
|
|
|
while (extern_table[h].ofs >= initial_ofs) {
|
1995-05-04 03:15:53 -07:00
|
|
|
if (extern_table[h].obj == v) {
|
1997-04-11 06:57:04 -07:00
|
|
|
byteoffset_t d = obj_counter - (extern_table[h].ofs - initial_ofs);
|
1995-05-04 03:15:53 -07:00
|
|
|
if (d < 0x100) {
|
1996-04-01 07:24:38 -08:00
|
|
|
writecode8(CODE_SHARED8, d);
|
1995-05-04 03:15:53 -07:00
|
|
|
} else if (d < 0x10000) {
|
1996-04-01 07:24:38 -08:00
|
|
|
writecode16(CODE_SHARED16, d);
|
1995-05-04 03:15:53 -07:00
|
|
|
} else {
|
1996-04-01 07:24:38 -08:00
|
|
|
writecode32(CODE_SHARED32, d);
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
h++;
|
|
|
|
if (h >= extern_table_size) h = 0;
|
|
|
|
}
|
|
|
|
/* Not seen yet. Record the object and output its contents. */
|
1997-04-11 06:57:04 -07:00
|
|
|
extern_table[h].ofs = initial_ofs + obj_counter;
|
1995-05-04 03:15:53 -07:00
|
|
|
extern_table[h].obj = v;
|
|
|
|
obj_counter++;
|
|
|
|
switch(tag) {
|
|
|
|
case String_tag: {
|
|
|
|
mlsize_t len = string_length(v);
|
|
|
|
if (len < 0x20) {
|
1996-04-01 07:24:38 -08:00
|
|
|
Write(PREFIX_SMALL_STRING + len);
|
1995-05-04 03:15:53 -07:00
|
|
|
} else if (len < 0x100) {
|
1996-04-01 07:24:38 -08:00
|
|
|
writecode8(CODE_STRING8, len);
|
1995-05-04 03:15:53 -07:00
|
|
|
} else {
|
1996-04-01 07:24:38 -08:00
|
|
|
writecode32(CODE_STRING32, len);
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
1996-04-01 07:24:38 -08:00
|
|
|
writeblock(String_val(v), len);
|
1995-05-04 03:15:53 -07:00
|
|
|
size_32 += 1 + (len + 4) / 4;
|
|
|
|
size_64 += 1 + (len + 8) / 8;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Double_tag: {
|
1997-04-11 06:57:04 -07:00
|
|
|
if (sizeof(double) != 8)
|
|
|
|
extern_invalid_argument("output_value: non-standard floats");
|
1996-04-01 07:24:38 -08:00
|
|
|
Write(CODE_DOUBLE_NATIVE);
|
|
|
|
writeblock((char *) v, 8);
|
1995-07-27 10:41:09 -07:00
|
|
|
size_32 += 1 + 2;
|
|
|
|
size_64 += 1 + 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Double_array_tag: {
|
|
|
|
mlsize_t nfloats;
|
1997-04-11 06:57:04 -07:00
|
|
|
if (sizeof(double) != 8)
|
|
|
|
extern_invalid_argument("output_value: non-standard floats");
|
1995-07-27 10:41:09 -07:00
|
|
|
nfloats = Wosize_val(v) / Double_wosize;
|
1995-07-28 05:23:27 -07:00
|
|
|
if (nfloats < 0x100) {
|
1996-04-01 07:24:38 -08:00
|
|
|
writecode8(CODE_DOUBLE_ARRAY8_NATIVE, nfloats);
|
1995-07-28 05:23:27 -07:00
|
|
|
} else {
|
1996-04-01 07:24:38 -08:00
|
|
|
writecode32(CODE_DOUBLE_ARRAY32_NATIVE, nfloats);
|
1995-07-28 05:23:27 -07:00
|
|
|
}
|
1996-04-01 07:24:38 -08:00
|
|
|
writeblock((char *) v, Bosize_val(v));
|
1995-07-27 10:41:09 -07:00
|
|
|
size_32 += 1 + nfloats * 2;
|
|
|
|
size_64 += 1 + nfloats;
|
1995-05-04 03:15:53 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Abstract_tag:
|
|
|
|
case Final_tag:
|
1997-04-11 06:57:04 -07:00
|
|
|
extern_invalid_argument("output_value: abstract value");
|
1995-05-04 03:15:53 -07:00
|
|
|
break;
|
|
|
|
case Closure_tag:
|
1995-07-10 02:48:27 -07:00
|
|
|
case Infix_tag:
|
1997-04-11 06:57:04 -07:00
|
|
|
extern_invalid_argument("output_value: functional value");
|
1995-05-04 03:15:53 -07:00
|
|
|
break;
|
1997-05-11 15:42:38 -07:00
|
|
|
case Object_tag:
|
|
|
|
extern_invalid_argument("output_value: object value");
|
|
|
|
break;
|
1995-05-04 03:15:53 -07:00
|
|
|
default: {
|
|
|
|
mlsize_t i;
|
|
|
|
if (tag < 16 && sz < 8) {
|
1996-04-01 07:24:38 -08:00
|
|
|
Write(PREFIX_SMALL_BLOCK + tag + (sz << 4));
|
1995-05-04 03:15:53 -07:00
|
|
|
} else {
|
1996-06-04 02:42:31 -07:00
|
|
|
writecode32(CODE_BLOCK32, hd & ~Black);
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
|
|
|
size_32 += 1 + sz;
|
|
|
|
size_64 += 1 + sz;
|
1996-04-01 07:24:38 -08:00
|
|
|
for (i = 0; i < sz - 1; i++) extern_rec(Field(v, i));
|
1995-05-04 03:15:53 -07:00
|
|
|
v = Field(v, i);
|
|
|
|
goto tailcall;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
1996-04-01 07:24:38 -08:00
|
|
|
static long extern_value(v)
|
1995-05-04 03:15:53 -07:00
|
|
|
value v;
|
|
|
|
{
|
1996-04-01 07:24:38 -08:00
|
|
|
long res_len;
|
1997-04-11 06:57:04 -07:00
|
|
|
|
|
|
|
/* Allocate buffer for holding the result */
|
1996-04-01 07:24:38 -08:00
|
|
|
alloc_extern_block();
|
1997-04-11 06:57:04 -07:00
|
|
|
/* Allocate hashtable of objects already seen, if needed */
|
1995-05-04 03:15:53 -07:00
|
|
|
extern_table_size = INITIAL_EXTERN_TABLE_SIZE;
|
1997-04-11 06:57:04 -07:00
|
|
|
if (extern_table == NULL) {
|
|
|
|
alloc_extern_table();
|
|
|
|
initial_ofs = 1;
|
|
|
|
}
|
1995-05-04 03:15:53 -07:00
|
|
|
obj_counter = 0;
|
|
|
|
size_32 = 0;
|
|
|
|
size_64 = 0;
|
1996-04-01 07:24:38 -08:00
|
|
|
/* Write magic number */
|
|
|
|
write32(Intext_magic_number);
|
|
|
|
/* Set aside space for the sizes */
|
|
|
|
extern_ptr += 4*4;
|
|
|
|
/* Marshal the object */
|
|
|
|
extern_rec(v);
|
1997-04-11 06:57:04 -07:00
|
|
|
/* Update initial offset for next call to extern_value(),
|
|
|
|
if we decide to keep the table of shared objects. */
|
|
|
|
initial_ofs += obj_counter;
|
|
|
|
/* Free the table of shared objects (if needed) */
|
|
|
|
free_extern_table();
|
1996-04-01 07:24:38 -08:00
|
|
|
/* Write the sizes */
|
1996-07-01 05:43:28 -07:00
|
|
|
#ifdef ARCH_SIXTYFOUR
|
1995-05-04 03:15:53 -07:00
|
|
|
if (size_32 >= (1L << 32) || size_64 >= (1L << 32)) {
|
|
|
|
/* The object is so big its size cannot be written in the header.
|
|
|
|
Besides, some of the block sizes or string lengths or shared offsets
|
|
|
|
it contains may have overflowed the 32 bits used to write them. */
|
|
|
|
failwith("output_value: object too big");
|
|
|
|
}
|
|
|
|
#endif
|
1996-04-01 07:24:38 -08:00
|
|
|
res_len = extern_ptr - extern_block;
|
|
|
|
extern_ptr = extern_block + 4;
|
|
|
|
write32(res_len - 5*4);
|
|
|
|
write32(obj_counter);
|
|
|
|
write32(size_32);
|
|
|
|
write32(size_64);
|
|
|
|
/* Result is res_len bytes starting at extern_block */
|
|
|
|
return res_len;
|
|
|
|
}
|
|
|
|
|
|
|
|
value output_value(chan, v) /* ML */
|
|
|
|
struct channel * chan;
|
|
|
|
value v;
|
|
|
|
{
|
|
|
|
long len;
|
|
|
|
len = extern_value(v);
|
1996-04-18 09:29:57 -07:00
|
|
|
really_putblock(chan, extern_block, len);
|
1996-04-01 07:24:38 -08:00
|
|
|
stat_free(extern_block);
|
1995-05-04 03:15:53 -07:00
|
|
|
return Val_unit;
|
|
|
|
}
|
1996-04-01 07:24:38 -08:00
|
|
|
|
|
|
|
value output_value_to_string(v) /* ML */
|
|
|
|
value v;
|
|
|
|
{
|
|
|
|
long len;
|
|
|
|
value res;
|
|
|
|
len = extern_value(v);
|
|
|
|
res = alloc_string(len);
|
|
|
|
bcopy(extern_block, String_val(res), len);
|
|
|
|
stat_free(extern_block);
|
|
|
|
return res;
|
|
|
|
}
|