mirror of
https://github.com/autc04/Retro68.git
synced 2024-09-09 12:54:40 +00:00
622 lines
17 KiB
C
622 lines
17 KiB
C
/*
|
|
* Copyright (c) 1993-1994 by Xerox Corporation. All rights reserved.
|
|
*
|
|
* THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
|
|
* OR IMPLIED. ANY USE IS AT YOUR OWN RISK.
|
|
*
|
|
* Permission is hereby granted to use or copy this program
|
|
* for any purpose, provided the above notices are retained on all copies.
|
|
* Permission to modify the code and to distribute modified code is granted,
|
|
* provided the above notices are retained, and a notice that the code was
|
|
* modified is included with the above copyright notice.
|
|
*
|
|
* Author: Hans-J. Boehm (boehm@parc.xerox.com)
|
|
*/
|
|
/*
|
|
* These are functions on cords that do not need to understand their
|
|
* implementation. They serve also serve as example client code for
|
|
* cord_basics.
|
|
*/
|
|
/* Boehm, December 8, 1995 1:53 pm PST */
|
|
# include <stdio.h>
|
|
# include <string.h>
|
|
# include <stdlib.h>
|
|
# include <stdarg.h>
|
|
# include "cord.h"
|
|
# include "ec.h"
|
|
# define I_HIDE_POINTERS /* So we get access to allocation lock. */
|
|
/* We use this for lazy file reading, */
|
|
/* so that we remain independent */
|
|
/* of the threads primitives. */
|
|
# include "gc.h"
|
|
|
|
/* For now we assume that pointer reads and writes are atomic, */
|
|
/* i.e. another thread always sees the state before or after */
|
|
/* a write. This might be false on a Motorola M68K with */
|
|
/* pointers that are not 32-bit aligned. But there probably */
|
|
/* aren't too many threads packages running on those. */
|
|
# define ATOMIC_WRITE(x,y) (x) = (y)
|
|
# define ATOMIC_READ(x) (*(x))
|
|
|
|
/* The standard says these are in stdio.h, but they aren't always: */
|
|
# ifndef SEEK_SET
|
|
# define SEEK_SET 0
|
|
# endif
|
|
# ifndef SEEK_END
|
|
# define SEEK_END 2
|
|
# endif
|
|
|
|
# define BUFSZ 2048 /* Size of stack allocated buffers when */
|
|
/* we want large buffers. */
|
|
|
|
typedef void (* oom_fn)(void);
|
|
|
|
# define OUT_OF_MEMORY { if (CORD_oom_fn != (oom_fn) 0) (*CORD_oom_fn)(); \
|
|
ABORT("Out of memory\n"); }
|
|
# define ABORT(msg) { fprintf(stderr, "%s\n", msg); abort(); }
|
|
|
|
CORD CORD_cat_char(CORD x, char c)
|
|
{
|
|
register char * string;
|
|
|
|
if (c == '\0') return(CORD_cat(x, CORD_nul(1)));
|
|
string = GC_MALLOC_ATOMIC(2);
|
|
if (string == 0) OUT_OF_MEMORY;
|
|
string[0] = c;
|
|
string[1] = '\0';
|
|
return(CORD_cat_char_star(x, string, 1));
|
|
}
|
|
|
|
CORD CORD_catn(int nargs, ...)
|
|
{
|
|
register CORD result = CORD_EMPTY;
|
|
va_list args;
|
|
register int i;
|
|
|
|
va_start(args, nargs);
|
|
for (i = 0; i < nargs; i++) {
|
|
register CORD next = va_arg(args, CORD);
|
|
result = CORD_cat(result, next);
|
|
}
|
|
va_end(args);
|
|
return(result);
|
|
}
|
|
|
|
typedef struct {
|
|
size_t len;
|
|
size_t count;
|
|
char * buf;
|
|
} CORD_fill_data;
|
|
|
|
int CORD_fill_proc(char c, void * client_data)
|
|
{
|
|
register CORD_fill_data * d = (CORD_fill_data *)client_data;
|
|
register size_t count = d -> count;
|
|
|
|
(d -> buf)[count] = c;
|
|
d -> count = ++count;
|
|
if (count >= d -> len) {
|
|
return(1);
|
|
} else {
|
|
return(0);
|
|
}
|
|
}
|
|
|
|
int CORD_batched_fill_proc(const char * s, void * client_data)
|
|
{
|
|
register CORD_fill_data * d = (CORD_fill_data *)client_data;
|
|
register size_t count = d -> count;
|
|
register size_t max = d -> len;
|
|
register char * buf = d -> buf;
|
|
register const char * t = s;
|
|
|
|
while((buf[count] = *t++) != '\0') {
|
|
count++;
|
|
if (count >= max) {
|
|
d -> count = count;
|
|
return(1);
|
|
}
|
|
}
|
|
d -> count = count;
|
|
return(0);
|
|
}
|
|
|
|
/* Fill buf with len characters starting at i. */
|
|
/* Assumes len characters are available. */
|
|
void CORD_fill_buf(CORD x, size_t i, size_t len, char * buf)
|
|
{
|
|
CORD_fill_data fd;
|
|
|
|
fd.len = len;
|
|
fd.buf = buf;
|
|
fd.count = 0;
|
|
(void)CORD_iter5(x, i, CORD_fill_proc, CORD_batched_fill_proc, &fd);
|
|
}
|
|
|
|
int CORD_cmp(CORD x, CORD y)
|
|
{
|
|
CORD_pos xpos;
|
|
CORD_pos ypos;
|
|
register size_t avail, yavail;
|
|
|
|
if (y == CORD_EMPTY) return(x != CORD_EMPTY);
|
|
if (x == CORD_EMPTY) return(-1);
|
|
if (CORD_IS_STRING(y) && CORD_IS_STRING(x)) return(strcmp(x,y));
|
|
CORD_set_pos(xpos, x, 0);
|
|
CORD_set_pos(ypos, y, 0);
|
|
for(;;) {
|
|
if (!CORD_pos_valid(xpos)) {
|
|
if (CORD_pos_valid(ypos)) {
|
|
return(-1);
|
|
} else {
|
|
return(0);
|
|
}
|
|
}
|
|
if (!CORD_pos_valid(ypos)) {
|
|
return(1);
|
|
}
|
|
if ((avail = CORD_pos_chars_left(xpos)) <= 0
|
|
|| (yavail = CORD_pos_chars_left(ypos)) <= 0) {
|
|
register char xcurrent = CORD_pos_fetch(xpos);
|
|
register char ycurrent = CORD_pos_fetch(ypos);
|
|
if (xcurrent != ycurrent) return(xcurrent - ycurrent);
|
|
CORD_next(xpos);
|
|
CORD_next(ypos);
|
|
} else {
|
|
/* process as many characters as we can */
|
|
register int result;
|
|
|
|
if (avail > yavail) avail = yavail;
|
|
result = strncmp(CORD_pos_cur_char_addr(xpos),
|
|
CORD_pos_cur_char_addr(ypos), avail);
|
|
if (result != 0) return(result);
|
|
CORD_pos_advance(xpos, avail);
|
|
CORD_pos_advance(ypos, avail);
|
|
}
|
|
}
|
|
}
|
|
|
|
int CORD_ncmp(CORD x, size_t x_start, CORD y, size_t y_start, size_t len)
|
|
{
|
|
CORD_pos xpos;
|
|
CORD_pos ypos;
|
|
register size_t count;
|
|
register long avail, yavail;
|
|
|
|
CORD_set_pos(xpos, x, x_start);
|
|
CORD_set_pos(ypos, y, y_start);
|
|
for(count = 0; count < len;) {
|
|
if (!CORD_pos_valid(xpos)) {
|
|
if (CORD_pos_valid(ypos)) {
|
|
return(-1);
|
|
} else {
|
|
return(0);
|
|
}
|
|
}
|
|
if (!CORD_pos_valid(ypos)) {
|
|
return(1);
|
|
}
|
|
if ((avail = CORD_pos_chars_left(xpos)) <= 0
|
|
|| (yavail = CORD_pos_chars_left(ypos)) <= 0) {
|
|
register char xcurrent = CORD_pos_fetch(xpos);
|
|
register char ycurrent = CORD_pos_fetch(ypos);
|
|
if (xcurrent != ycurrent) return(xcurrent - ycurrent);
|
|
CORD_next(xpos);
|
|
CORD_next(ypos);
|
|
count++;
|
|
} else {
|
|
/* process as many characters as we can */
|
|
register int result;
|
|
|
|
if (avail > yavail) avail = yavail;
|
|
count += avail;
|
|
if (count > len) avail -= (count - len);
|
|
result = strncmp(CORD_pos_cur_char_addr(xpos),
|
|
CORD_pos_cur_char_addr(ypos), (size_t)avail);
|
|
if (result != 0) return(result);
|
|
CORD_pos_advance(xpos, (size_t)avail);
|
|
CORD_pos_advance(ypos, (size_t)avail);
|
|
}
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
char * CORD_to_char_star(CORD x)
|
|
{
|
|
register size_t len = CORD_len(x);
|
|
char * result = GC_MALLOC_ATOMIC(len + 1);
|
|
|
|
if (result == 0) OUT_OF_MEMORY;
|
|
CORD_fill_buf(x, 0, len, result);
|
|
result[len] = '\0';
|
|
return(result);
|
|
}
|
|
|
|
CORD CORD_from_char_star(const char *s)
|
|
{
|
|
char * result;
|
|
size_t len = strlen(s);
|
|
|
|
if (0 == len) return(CORD_EMPTY);
|
|
result = GC_MALLOC_ATOMIC(len + 1);
|
|
if (result == 0) OUT_OF_MEMORY;
|
|
memcpy(result, s, len+1);
|
|
return(result);
|
|
}
|
|
|
|
const char * CORD_to_const_char_star(CORD x)
|
|
{
|
|
if (x == 0) return("");
|
|
if (CORD_IS_STRING(x)) return((const char *)x);
|
|
return(CORD_to_char_star(x));
|
|
}
|
|
|
|
char CORD_fetch(CORD x, size_t i)
|
|
{
|
|
CORD_pos xpos;
|
|
|
|
CORD_set_pos(xpos, x, i);
|
|
if (!CORD_pos_valid(xpos)) ABORT("bad index?");
|
|
return(CORD_pos_fetch(xpos));
|
|
}
|
|
|
|
|
|
int CORD_put_proc(char c, void * client_data)
|
|
{
|
|
register FILE * f = (FILE *)client_data;
|
|
|
|
return(putc(c, f) == EOF);
|
|
}
|
|
|
|
int CORD_batched_put_proc(const char * s, void * client_data)
|
|
{
|
|
register FILE * f = (FILE *)client_data;
|
|
|
|
return(fputs(s, f) == EOF);
|
|
}
|
|
|
|
|
|
int CORD_put(CORD x, FILE * f)
|
|
{
|
|
if (CORD_iter5(x, 0, CORD_put_proc, CORD_batched_put_proc, f)) {
|
|
return(EOF);
|
|
} else {
|
|
return(1);
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
size_t pos; /* Current position in the cord */
|
|
char target; /* Character we're looking for */
|
|
} chr_data;
|
|
|
|
int CORD_chr_proc(char c, void * client_data)
|
|
{
|
|
register chr_data * d = (chr_data *)client_data;
|
|
|
|
if (c == d -> target) return(1);
|
|
(d -> pos) ++;
|
|
return(0);
|
|
}
|
|
|
|
int CORD_rchr_proc(char c, void * client_data)
|
|
{
|
|
register chr_data * d = (chr_data *)client_data;
|
|
|
|
if (c == d -> target) return(1);
|
|
(d -> pos) --;
|
|
return(0);
|
|
}
|
|
|
|
int CORD_batched_chr_proc(const char *s, void * client_data)
|
|
{
|
|
register chr_data * d = (chr_data *)client_data;
|
|
register char * occ = strchr(s, d -> target);
|
|
|
|
if (occ == 0) {
|
|
d -> pos += strlen(s);
|
|
return(0);
|
|
} else {
|
|
d -> pos += occ - s;
|
|
return(1);
|
|
}
|
|
}
|
|
|
|
size_t CORD_chr(CORD x, size_t i, int c)
|
|
{
|
|
chr_data d;
|
|
|
|
d.pos = i;
|
|
d.target = c;
|
|
if (CORD_iter5(x, i, CORD_chr_proc, CORD_batched_chr_proc, &d)) {
|
|
return(d.pos);
|
|
} else {
|
|
return(CORD_NOT_FOUND);
|
|
}
|
|
}
|
|
|
|
size_t CORD_rchr(CORD x, size_t i, int c)
|
|
{
|
|
chr_data d;
|
|
|
|
d.pos = i;
|
|
d.target = c;
|
|
if (CORD_riter4(x, i, CORD_rchr_proc, &d)) {
|
|
return(d.pos);
|
|
} else {
|
|
return(CORD_NOT_FOUND);
|
|
}
|
|
}
|
|
|
|
/* Find the first occurrence of s in x at position start or later. */
|
|
/* This uses an asymptotically poor algorithm, which should typically */
|
|
/* perform acceptably. We compare the first few characters directly, */
|
|
/* and call CORD_ncmp whenever there is a partial match. */
|
|
/* This has the advantage that we allocate very little, or not at all. */
|
|
/* It's very fast if there are few close misses. */
|
|
size_t CORD_str(CORD x, size_t start, CORD s)
|
|
{
|
|
CORD_pos xpos;
|
|
size_t xlen = CORD_len(x);
|
|
size_t slen;
|
|
register size_t start_len;
|
|
const char * s_start;
|
|
unsigned long s_buf = 0; /* The first few characters of s */
|
|
unsigned long x_buf = 0; /* Start of candidate substring. */
|
|
/* Initialized only to make compilers */
|
|
/* happy. */
|
|
unsigned long mask = 0;
|
|
register size_t i;
|
|
register size_t match_pos;
|
|
|
|
if (s == CORD_EMPTY) return(start);
|
|
if (CORD_IS_STRING(s)) {
|
|
s_start = s;
|
|
slen = strlen(s);
|
|
} else {
|
|
s_start = CORD_to_char_star(CORD_substr(s, 0, sizeof(unsigned long)));
|
|
slen = CORD_len(s);
|
|
}
|
|
if (xlen < start || xlen - start < slen) return(CORD_NOT_FOUND);
|
|
start_len = slen;
|
|
if (start_len > sizeof(unsigned long)) start_len = sizeof(unsigned long);
|
|
CORD_set_pos(xpos, x, start);
|
|
for (i = 0; i < start_len; i++) {
|
|
mask <<= 8;
|
|
mask |= 0xff;
|
|
s_buf <<= 8;
|
|
s_buf |= (unsigned char)s_start[i];
|
|
x_buf <<= 8;
|
|
x_buf |= (unsigned char)CORD_pos_fetch(xpos);
|
|
CORD_next(xpos);
|
|
}
|
|
for (match_pos = start; ; match_pos++) {
|
|
if ((x_buf & mask) == s_buf) {
|
|
if (slen == start_len ||
|
|
CORD_ncmp(x, match_pos + start_len,
|
|
s, start_len, slen - start_len) == 0) {
|
|
return(match_pos);
|
|
}
|
|
}
|
|
if ( match_pos == xlen - slen ) {
|
|
return(CORD_NOT_FOUND);
|
|
}
|
|
x_buf <<= 8;
|
|
x_buf |= (unsigned char)CORD_pos_fetch(xpos);
|
|
CORD_next(xpos);
|
|
}
|
|
}
|
|
|
|
void CORD_ec_flush_buf(CORD_ec x)
|
|
{
|
|
register size_t len = x[0].ec_bufptr - x[0].ec_buf;
|
|
char * s;
|
|
|
|
if (len == 0) return;
|
|
s = GC_MALLOC_ATOMIC(len+1);
|
|
memcpy(s, x[0].ec_buf, len);
|
|
s[len] = '\0';
|
|
x[0].ec_cord = CORD_cat_char_star(x[0].ec_cord, s, len);
|
|
x[0].ec_bufptr = x[0].ec_buf;
|
|
}
|
|
|
|
void CORD_ec_append_cord(CORD_ec x, CORD s)
|
|
{
|
|
CORD_ec_flush_buf(x);
|
|
x[0].ec_cord = CORD_cat(x[0].ec_cord, s);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
char CORD_nul_func(size_t i, void * client_data)
|
|
{
|
|
return((char)(unsigned long)client_data);
|
|
}
|
|
|
|
|
|
CORD CORD_chars(char c, size_t i)
|
|
{
|
|
return(CORD_from_fn(CORD_nul_func, (void *)(unsigned long)c, i));
|
|
}
|
|
|
|
CORD CORD_from_file_eager(FILE * f)
|
|
{
|
|
register int c;
|
|
CORD_ec ecord;
|
|
|
|
CORD_ec_init(ecord);
|
|
for(;;) {
|
|
c = getc(f);
|
|
if (c == 0) {
|
|
/* Append the right number of NULs */
|
|
/* Note that any string of NULs is rpresented in 4 words, */
|
|
/* independent of its length. */
|
|
register size_t count = 1;
|
|
|
|
CORD_ec_flush_buf(ecord);
|
|
while ((c = getc(f)) == 0) count++;
|
|
ecord[0].ec_cord = CORD_cat(ecord[0].ec_cord, CORD_nul(count));
|
|
}
|
|
if (c == EOF) break;
|
|
CORD_ec_append(ecord, c);
|
|
}
|
|
(void) fclose(f);
|
|
return(CORD_balance(CORD_ec_to_cord(ecord)));
|
|
}
|
|
|
|
/* The state maintained for a lazily read file consists primarily */
|
|
/* of a large direct-mapped cache of previously read values. */
|
|
/* We could rely more on stdio buffering. That would have 2 */
|
|
/* disadvantages: */
|
|
/* 1) Empirically, not all fseek implementations preserve the */
|
|
/* buffer whenever they could. */
|
|
/* 2) It would fail if 2 different sections of a long cord */
|
|
/* were being read alternately. */
|
|
/* We do use the stdio buffer for read ahead. */
|
|
/* To guarantee thread safety in the presence of atomic pointer */
|
|
/* writes, cache lines are always replaced, and never modified in */
|
|
/* place. */
|
|
|
|
# define LOG_CACHE_SZ 14
|
|
# define CACHE_SZ (1 << LOG_CACHE_SZ)
|
|
# define LOG_LINE_SZ 9
|
|
# define LINE_SZ (1 << LOG_LINE_SZ)
|
|
|
|
typedef struct {
|
|
size_t tag;
|
|
char data[LINE_SZ];
|
|
/* data[i%LINE_SZ] = ith char in file if tag = i/LINE_SZ */
|
|
} cache_line;
|
|
|
|
typedef struct {
|
|
FILE * lf_file;
|
|
size_t lf_current; /* Current file pointer value */
|
|
cache_line * volatile lf_cache[CACHE_SZ/LINE_SZ];
|
|
} lf_state;
|
|
|
|
# define MOD_CACHE_SZ(n) ((n) & (CACHE_SZ - 1))
|
|
# define DIV_CACHE_SZ(n) ((n) >> LOG_CACHE_SZ)
|
|
# define MOD_LINE_SZ(n) ((n) & (LINE_SZ - 1))
|
|
# define DIV_LINE_SZ(n) ((n) >> LOG_LINE_SZ)
|
|
# define LINE_START(n) ((n) & ~(LINE_SZ - 1))
|
|
|
|
typedef struct {
|
|
lf_state * state;
|
|
size_t file_pos; /* Position of needed character. */
|
|
cache_line * new_cache;
|
|
} refill_data;
|
|
|
|
/* Executed with allocation lock. */
|
|
static char refill_cache(client_data)
|
|
refill_data * client_data;
|
|
{
|
|
register lf_state * state = client_data -> state;
|
|
register size_t file_pos = client_data -> file_pos;
|
|
FILE *f = state -> lf_file;
|
|
size_t line_start = LINE_START(file_pos);
|
|
size_t line_no = DIV_LINE_SZ(MOD_CACHE_SZ(file_pos));
|
|
cache_line * new_cache = client_data -> new_cache;
|
|
|
|
if (line_start != state -> lf_current
|
|
&& fseek(f, line_start, SEEK_SET) != 0) {
|
|
ABORT("fseek failed");
|
|
}
|
|
if (fread(new_cache -> data, sizeof(char), LINE_SZ, f)
|
|
<= file_pos - line_start) {
|
|
ABORT("fread failed");
|
|
}
|
|
new_cache -> tag = DIV_LINE_SZ(file_pos);
|
|
/* Store barrier goes here. */
|
|
ATOMIC_WRITE(state -> lf_cache[line_no], new_cache);
|
|
state -> lf_current = line_start + LINE_SZ;
|
|
return(new_cache->data[MOD_LINE_SZ(file_pos)]);
|
|
}
|
|
|
|
char CORD_lf_func(size_t i, void * client_data)
|
|
{
|
|
register lf_state * state = (lf_state *)client_data;
|
|
register cache_line * volatile * cl_addr =
|
|
&(state -> lf_cache[DIV_LINE_SZ(MOD_CACHE_SZ(i))]);
|
|
register cache_line * cl = (cache_line *)ATOMIC_READ(cl_addr);
|
|
|
|
if (cl == 0 || cl -> tag != DIV_LINE_SZ(i)) {
|
|
/* Cache miss */
|
|
refill_data rd;
|
|
|
|
rd.state = state;
|
|
rd.file_pos = i;
|
|
rd.new_cache = GC_NEW_ATOMIC(cache_line);
|
|
if (rd.new_cache == 0) OUT_OF_MEMORY;
|
|
return((char)(GC_word)
|
|
GC_call_with_alloc_lock((GC_fn_type) refill_cache, &rd));
|
|
}
|
|
return(cl -> data[MOD_LINE_SZ(i)]);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
void CORD_lf_close_proc(void * obj, void * client_data)
|
|
{
|
|
if (fclose(((lf_state *)obj) -> lf_file) != 0) {
|
|
ABORT("CORD_lf_close_proc: fclose failed");
|
|
}
|
|
}
|
|
|
|
CORD CORD_from_file_lazy_inner(FILE * f, size_t len)
|
|
{
|
|
register lf_state * state = GC_NEW(lf_state);
|
|
register int i;
|
|
|
|
if (state == 0) OUT_OF_MEMORY;
|
|
if (len != 0) {
|
|
/* Dummy read to force buffer allocation. */
|
|
/* This greatly increases the probability */
|
|
/* of avoiding deadlock if buffer allocation */
|
|
/* is redirected to GC_malloc and the */
|
|
/* world is multithreaded. */
|
|
char buf[1];
|
|
|
|
(void) fread(buf, 1, 1, f);
|
|
rewind(f);
|
|
}
|
|
state -> lf_file = f;
|
|
for (i = 0; i < CACHE_SZ/LINE_SZ; i++) {
|
|
state -> lf_cache[i] = 0;
|
|
}
|
|
state -> lf_current = 0;
|
|
GC_REGISTER_FINALIZER(state, CORD_lf_close_proc, 0, 0, 0);
|
|
return(CORD_from_fn(CORD_lf_func, state, len));
|
|
}
|
|
|
|
CORD CORD_from_file_lazy(FILE * f)
|
|
{
|
|
register long len;
|
|
|
|
if (fseek(f, 0l, SEEK_END) != 0) {
|
|
ABORT("Bad fd argument - fseek failed");
|
|
}
|
|
if ((len = ftell(f)) < 0) {
|
|
ABORT("Bad fd argument - ftell failed");
|
|
}
|
|
rewind(f);
|
|
return(CORD_from_file_lazy_inner(f, (size_t)len));
|
|
}
|
|
|
|
# define LAZY_THRESHOLD (128*1024 + 1)
|
|
|
|
CORD CORD_from_file(FILE * f)
|
|
{
|
|
register long len;
|
|
|
|
if (fseek(f, 0l, SEEK_END) != 0) {
|
|
ABORT("Bad fd argument - fseek failed");
|
|
}
|
|
if ((len = ftell(f)) < 0) {
|
|
ABORT("Bad fd argument - ftell failed");
|
|
}
|
|
rewind(f);
|
|
if (len < LAZY_THRESHOLD) {
|
|
return(CORD_from_file_eager(f));
|
|
} else {
|
|
return(CORD_from_file_lazy_inner(f, (size_t)len));
|
|
}
|
|
}
|