/* fasl.c Copyright (C) 1990-2002 Codemist Ltd */
/*
* Binary file support for faster loading of precompiled code etc.
*/
/*
* This code may be used and modified, and redistributed in binary
* or source form, subject to the "CCL Public License", which should
* accompany it. This license is a variant on the BSD license, and thus
* permits use of code derived from this in either open and commercial
* projects: but it does require that updates to this code be made
* available back to the originators of the package.
* Before merging other code in with this or linking this code
* with other packages or libraries please check that the license terms
* of the other material are compatible with those of this.
*/
/* Signature: 732b5f7b 10-Oct-2002 */
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include "machine.h"
#include "tags.h"
#include "cslerror.h"
#include "externs.h"
#include "read.h"
#include "stream.h"
#include "arith.h"
#include "entries.h"
#ifdef TIMEOUT
#include "timeout.h"
#endif
#ifdef SOCKETS
#include "sockhdr.h"
#endif
CSLbool fasl_output_file = NO; /* An output file is open? */
static int skipping_input = 0, skipping_output = 0;
static int32 recent_pointer = 0, hits = 0 , misses = 0, fasl_byte_count = 0;
static CSLbool fp_rep_set = NO;
/*
* FASL files are binary, and are treated as containing sequences of
* unsigned bytes, where the bytes are names as in the following set
* of definitions, which MUST be kept in step with the code that
* creates FASL files. I expect FASL files to be portable between
* computers that use the same character set, but names of symbols
* will get totally scrambled between ASCII and EBCDIC hosts.
*/
#define F_END 0 /* end of FASL file */
#define F_NIL 1 /* the symbol NIL */
#define F_TRU 2 /* the symbol T */
#define F_EXT 3 /* used to get operands > 8 bits into other codes */
#define F_INT 4 /* positive fixnum */
#define F_NEG 5 /* negative fixnum */
#define F_BIG 6 /* bignum */
#define F_RAT 7 /* ratio */
#define F_CPX 8 /* complex number */
#define F_FPS 9 /* short float */
#define F_FPF 10 /* single float */
#define F_FPD 11 /* double float */
#define F_FPL 12 /* long float */
#define F_SYM 13 /* symbol, general length */
#define F_ID1 14 /* symbol with 1-character name */
#define F_ID2 15 /* symbol with 2-character name */
#define F_ID3 16 /* etc */
#define F_ID4 17
#define F_ID5 18
#define F_ID6 19
#define F_ID7 20
#define F_ID8 21
#define F_ID9 22
#define F_IDA 23
#define F_IDB 24
#define F_IDC 25
#define F_IDD 26
#define F_IDE 27
#define F_IDF 28 /* symbol with 15 character name */
#define F_STR 29 /* string */
#define F_BP0 30 /* bytecode string for binary code (0 - 255 bytes) */
#define F_BP1 31 /* 256 - 511 bytes of BPS */
#define F_BP2 32 /* 512 - 767 bytes of BPS */
#define F_BP3 33 /* 768 - 1023 bytes of BPS */
#define F_HASH 34 /* hash table */
#define F_VEC 35 /* simple Lisp vector */
#define F_LST 36 /* list, general length */
#define F_LS1 37 /* list of length 1 */
#define F_LS2 38 /* list of length 2 */
#define F_LS3 39 /* list of length 3 */
#define F_LS4 40 /* list of length 4 */
#define F_DOT 41 /* list ending with dotted item */
#define F_QUT 42 /* (QUOTE xx) */
#define F_DEF0 43 /* function definition, 0 args */
#define F_DEF1 44 /* function definition, 1 arg */
#define F_DEF2 45 /* function definition, 2 args */
#define F_DEF3 46 /* function definition, 3 args */
#define F_DEFN 47 /* function definition, 4 or more args */
#define F_REP 48 /* followed by 2 bytes giving FP rep */
#define F_CHAR 49 /* bits, font, code */
#define F_SDEF 50 /* associated with fn definition - Lisp coded version */
#define F_STRUCT 51 /* Structure or e-vector */
#define F_DEFOPT 52 /* function definition, &optional args */
#define F_DEFHOPT 53 /* function definition, &optional args + initform */
#define F_DEFREST 54 /* function definition, &optional/&rest args */
#define F_DEFHREST 55 /* function definition, &optional/&rest + initform */
#define F_ARRAY 56 /* Common Lisp style general array */
#define F_BITVEC 57 /* Bit-vector */
#ifdef COMMON
#define F_PKGINT 58 /* abc::def (coded as m, n, c1..cm, c1..cn) */
/* m=0 can be used for gensyms, as in #:xxx */
#else
#define F_GENSYM 58 /* coded as n, c1..cn. Eg just like PKGINT,0 */
#endif
#define F_PKGEXT 59 /* abc:def (m=0 => keyword) */
#define F_OLD 60 /* all remaining codes denote recently seen symbols */
#define KEEP_RECENT (256 - F_OLD)
#define MAX_OBJECT 256 /* limit on symbol & number length */
#ifdef DEBUG_FASL
static char *fasl_code_names[] =
{
"END", "NIL", "TRU", "EXT",
"INT", "NEG", "BIG", "RAT",
"CPX", "FPS", "FPF", "FPD",
"FPL", "SYM", "ID1", "ID2",
"ID3", "ID4", "ID5", "ID6",
"ID7", "ID8", "ID9", "IDA",
"IDB", "IDC", "IDD", "IDE",
"IDF", "STR", "BP0", "BP1",
"BP2", "BP3", "HASH", "VEC",
"LST", "LS1", "LS2", "LS3",
"LS4", "DOT", "QUT", "DEF0",
"DEF1", "DEF2", "DEF3", "DEFN",
"REP", "CHAR", "SDEF", "STRUCT",
"DEFOPT", "DEFHOPT", "DEFREST", "DEFHREST",
#ifdef COMMON
"ARRAY", "BITVEC", "PKGINT", "PKGEXT"
#else
"ARRAY", "BITVEC", "GENSYM", "PKGEXT"
#endif
};
static char old_name[8];
static char *fasl_code(int n)
{
if (n >= F_OLD)
{ sprintf(old_name, "OLD%d", n - F_OLD);
return old_name;
}
else return fasl_code_names[n];
}
#endif
#define boffo_char(i) celt(boffo, i)
static int fp_rep = 0; /* representation used when FASL file was written */
static Lisp_Object fastread(void);
#ifdef COMMON
static char package_name[256];
#endif
#ifdef DEBUG_FASL
static int IgetcDebug()
{
int k = Igetc();
trace_printf("Igetc = %d/%.2x/%s\n", k, k, fasl_code(k));
return k;
}
#define Igetc() IgetcDebug()
static int IreadDebug(char *x, int n)
{
int i;
int k = Iread(x, n);
trace_printf("Iread(%d) = %d:", n, k);
for (i=0; i<k; i++)
{ trace_printf(" %d/%x", x[i], x[i]);
if (32 <= x[i] && x[i] < 0x7f) trace_printf("/'%c'", x[i]);
}
trace_printf("\n");
return k;
}
#define Iread(a, n) IreadDebug(a, n)
#endif
static Lisp_Object fastread1(int32 ch, int32 operand)
{
Lisp_Object nil = C_nil;
Lisp_Object r = nil, w;
#ifdef COMMON
int operand0;
#endif
int32 p;
switch (ch)
{
default: /* a recently-mentioned item */
if (ch < F_OLD)
{ err_printf("\nError at byte %ld : %#.2x/%d\n",
(long)fasl_byte_count, ch & 0xff, ch & 0xff);
return aerror("bad byte in FASL file");
}
if (operand != 0)
{ operand = ((operand-1) << 7) + (ch - F_OLD);
r = faslgensyms;
while (operand != 0)
{ r = qcdr(r);
operand--;
}
return qcar(r);
}
operand = recent_pointer - (ch - F_OLD);
if (operand < 0) operand += KEEP_RECENT;
r = elt(faslvec, operand);
return r;
#ifdef COMMON
case F_PKGINT:
case F_PKGEXT:
{ int ch1 = Igetc();
fasl_byte_count++;
if (ch1 == EOF) return aerror("premature EOF in FASL file");
operand0 = ch1 & 0xff;
ch1 = Igetc();
if (ch1 == EOF) return aerror("premature EOF in FASL file");
operand = (operand << 8) + ((int32)ch1 & 0xff);
if (operand0 != 0)
{ if (Iread(package_name, operand0) != operand0)
return aerror("FASL file corrupted");
fasl_byte_count += operand0;
r = find_package(package_name, operand0);
if (r == nil)
{ err_printf(
"+++ Package %s not found, using current package\n",
package_name);
r = CP;
}
}
else r = qvalue(keyword_package);
if (Iread(&boffo_char(0), operand) != operand)
return aerror("FASL file corrupted");
fasl_byte_count += operand;
if (skipping_input == 2) r = nil;
else if (ch == F_PKGINT)
{ if (operand0 == 0)
{ r = iintern(boffo, (int32)operand, CP, 0);
errexit();
r = Lgensym2(nil, r);
}
else r = iintern(boffo, (int32)operand, r, 0);
}
else if (r == qvalue(keyword_package))
r = iintern(boffo, (int32)operand, r, 0);
else
{ push(r);
w = iintern(boffo, (int32)operand, r, 4);
pop(r);
errexit();
if (mv_2 == nil)
{ err_printf("+++ Symbol %.*s not external in %s\n",
(int)operand, &celt(boffo, 0), package_name);
err_printf("+++ Treating as an internal symbol...\n");
w = iintern(boffo, (int32)operand, r, 0);
}
r = w;
}
errexit();
/*
* The KEEP_RECENT most recently used symbols are stored in a cyclic buffer
* so that if re-used they will be rapidly available. See comment under
* F_GENSYM for a delicacy here.
*/
if (skipping_input == 0 ||
(ch == F_PKGINT && operand0 == 0)) /* NB keep gensyms! */
{ recent_pointer++;
if (recent_pointer == KEEP_RECENT) recent_pointer = 0;
w = elt(faslvec, recent_pointer);
if (qpackage(w) == nil) /* eg a gensym */
{ push(r);
#ifdef DEBUG_FASL
trace_printf("recording gensym ");
prin_to_trace(w);
trace_printf("\n");
#endif
w = cons(w, faslgensyms);
pop(r);
errexit();
faslgensyms = w;
}
elt(faslvec, recent_pointer) = r;
#ifdef DEBUG_FASL
trace_printf("recording ");
prin_to_trace(r);
trace_printf("\n");
#endif
}
return r;
}
#else /* COMMON */
case F_GENSYM:
{ int ch1 = Igetc();
if (ch1 == EOF) return aerror("premature EOF in FASL file");
operand = (operand << 8) + ((int32)ch1 & 0xff);
if (Iread(&boffo_char(0), operand) != operand)
return aerror("FASL file corrupted");
fasl_byte_count += operand;
if (skipping_input == 2) r = nil;
r = iintern(boffo, (int32)operand, CP, 0);
errexit();
r = Lgensym2(nil, r);
errexit();
/*
* The KEEP_RECENT most recently used symbols are stored in a cyclic buffer
* so that if re-used they will be rapidly available. Note as a real curiosity
* then gensyms will be stored in this even if skipping_input is non-zero.
* this is essential so that gensyms within saved-definitions are
* can get processed properly. Specifically so that repeated use of a gensym
* within a saved definition leads to two references to the same thing
* rather than to the creation of two new gensyms. The same issue should
* arise for un-interned Common Lisp symbols.
*/
recent_pointer++;
if (recent_pointer == KEEP_RECENT) recent_pointer = 0;
w = elt(faslvec, recent_pointer);
if (qheader(w) & SYM_ANY_GENSYM)
{ push(r);
#ifdef DEBUG_FASL
trace_printf("recording gensym ");
prin_to_trace(w);
trace_printf("\n");
#endif
w = cons(w, faslgensyms);
pop(r);
errexit();
faslgensyms = w;
}
elt(faslvec, recent_pointer) = r;
#ifdef DEBUG_FASL
trace_printf("recording ");
prin_to_trace(r);
trace_printf("\n");
#endif
return r;
}
#endif /* COMMON */
/* these all have a 1-byte arg to follow */
case F_INT:
case F_NEG:
case F_BIG:
case F_SYM:
case F_STR:
case F_BP0:
case F_BP1:
case F_BP2:
case F_BP3:
case F_HASH:
case F_VEC:
case F_STRUCT:
case F_LST:
case F_DOT:
{ int ch1 = Igetc();
fasl_byte_count++;
if (ch1 == EOF) return aerror("premature EOF in FASL file");
operand = (operand << 8) + ((int32)ch1 & 0xff);
}
switch (ch)
{
default: /* can never occur */
case F_INT: /* positive fixnum */
return fixnum_of_int(operand);
case F_NEG: /* negative fixnum */
return fixnum_of_int(-operand);
case F_BIG:
r = getvector(TAG_NUMBERS, TYPE_BIGNUM, CELL+operand);
/* I tidy up the padding word if needbe */
#ifdef ADDRESS_64
if ((operand & 4) != 0)
#endif
if ((operand & 4) == 0)
*(int32 *)((char *)r + CELL + 4 - TAG_NUMBERS + operand) = 0;
/*
* I accumulate the numeric components of the bignum here by steam - one
* byte at a time - so that fasl files made on a machine with one byte-order
* can be used on machines with the other. I do not expect that there
* will be many bignums in fasl files, and thus this is not a performance
* critical area.
*/
{ int32 i;
for (i = 0; i<operand; i+=4)
{ unsigned32 v = (int32)Igetc() & 0xff;
v = (v << 8) | ((int32)Igetc() & 0xff);
v = (v << 8) | ((int32)Igetc() & 0xff);
v = (v << 8) | ((int32)Igetc() & 0xff);
*(unsigned32 *)((char *)r + CELL - TAG_NUMBERS + i) = v;
fasl_byte_count += 4;
}
}
return r;
case F_SYM: /* n characters making a symbol */
if (Iread(&boffo_char(0), operand) != operand)
return aerror("FASL file corrupted");
fasl_byte_count += operand;
/*
* skipping_input is usually zero. If it is 1 then I read in expressions
* as normal save that I do not update the recently-mentioned-symbol cache.
* skipping_input==2 causes me to parse the input FASL file but not
* return a useful result. Well actually everything will be read in
* as normal save that symbols will all be mapped onto NIL.
*/
if (skipping_input == 2) r = nil;
else r = iintern(boffo, operand, CP, 0);
errexit();
/*
* The KEEP_RECENT most recently used symbols are stored in a cyclic buffer
* so that if re-used they will be rapidly available.
*/
if (skipping_input == 0)
{ recent_pointer++;
if (recent_pointer == KEEP_RECENT) recent_pointer = 0;
w = elt(faslvec, recent_pointer);
#ifdef COMMON
if (qpackage(w) == nil)
#else
if (qheader(w) & SYM_ANY_GENSYM)
#endif
{ push(r);
#ifdef DEBUG_FASL
trace_printf("recording gensym ");
prin_to_trace(w);
trace_printf("\n");
#endif
w = cons(w, faslgensyms);
pop(r);
errexit();
faslgensyms = w;
}
elt(faslvec, recent_pointer) = r;
#ifdef DEBUG_FASL
trace_printf("recording ");
prin_to_trace(r);
trace_printf("\n");
#endif
}
return r;
case F_STR: /* n characters making a string */
r = getvector(TAG_VECTOR, TYPE_STRING, CELL+operand);
errexit();
{ char *s = (char *)r - TAG_VECTOR + CELL;
int l = operand & 7;
#ifdef ADDRESS_64
switch (l)
{
case 1:
case 2:
case 3: *(int32 *)(s + operand - l) = 0;
case 4:
case 5:
case 6:
case 7: *(int32 *)(s + operand - l + 4) = 0;
case 0: break;
}
#else
switch (l)
{
case 5:
case 6:
case 7: *(int32 *)(s + operand - l + 8) = 0;
case 0:
case 1:
case 2:
case 3: *(int32 *)(s + operand - l + 4) = 0;
case 4: break;
}
#endif
if (Iread(s, operand) != operand)
return aerror("FASL file corrupted");
fasl_byte_count += operand;
}
return r;
case F_BP3: /* n + 768 bytes of BPS */
operand += 256;
/* drop through */
case F_BP2: /* n + 512 bytes of BPS */
operand += 256;
/* drop through */
case F_BP1: /* n + 256 bytes of BPS */
operand += 256;
/* drop through */
case F_BP0: /* n bytes making BPS */
/* See the other place where qvalue(savedef) == savedef is tested. */
if (qvalue(savedef) == savedef)
{ int32 i;
for (i=0; i<operand; i++) Igetc();
fasl_byte_count += operand;
return nil;
}
else
{ r = getcodevector(TYPE_BPS, operand+CELL);
errexit();
if (Iread(data_of_bps(r), operand) != operand)
return aerror("FASL file corrupted");
fasl_byte_count += operand;
return r;
}
case F_HASH:
case F_STRUCT:
case F_VEC: /* normal vector with n entries */
r = getvector_init(CELL*(operand+1), nil);
errexit();
if (ch == F_STRUCT)
vechdr(r) ^= (TYPE_STRUCTURE ^ TYPE_SIMPLE_VEC);
else if (ch == F_HASH)
vechdr(r) ^= (TYPE_HASH ^ TYPE_SIMPLE_VEC);
for (p=0; p<operand; p++)
{ push(r);
w = fastread();
pop(r);
errexit();
elt(r, p) = w;
}
if (ch == F_HASH)
{
/*
* If I have just read in a hash table that was built on EQ or EQL I will
* need to rehash it now.
*/
if (elt(r, 0) == fixnum_of_int(0) ||
elt(r, 0) == fixnum_of_int(1) ||
!is_fixnum(elt(r, 0)))
{ Lisp_Object v;
rehash_this_table(v = elt(r, 4));
push(r);
v = ncons(v);
pop(r);
errexit();
qcdr(v) = eq_hash_tables;
eq_hash_tables = v;
}
}
return r;
case F_LST: /* build list of length n */
case F_DOT: /* dotted list with n values */
if (ch == F_LST) r = nil;
else
{ r = fastread();
errexit();
}
for (p = 0; p<operand; p++)
{ push(r);
w = fastread();
pop(r);
errexit();
r = cons(w, r);
errexit();
}
return r;
}
}
}
static CSLbool just_reading_source = NO;
static Lisp_Object fastread(void)
{
int32 operand = 0, ch = Igetc();
Lisp_Object nil = C_nil;
Lisp_Object r = nil, w;
fasl_byte_count++;
if (ch == EOF) return aerror("premature EOF in FASL file");
ch &= 0xff;
for (;;)
{
switch (ch)
{
case F_END: /* marks end of file */
return CHAR_EOF;
case F_NIL: /* represents the value NIL */
return nil;
case F_TRU: /* represents the value T */
return lisp_true;
case F_QUT: /* (QUOTE <next thing>) */
r = fastread();
errexit();
return list2(quote_symbol, r);
case F_SDEF:
/*
* I am THINKING about an option that avoids reading in definitions here
* when *SAVEDEF is nil, and just skips the bytes in the FASL file. The
* problem with doing so is that of the table of recently referred to
* symbols - which must be kept in step between FASL writing and reading
* whether or not *SAVEDEF is active.
*/
if (qvalue(savedef) == nil) skipping_input = 2;
else skipping_input = 1;
#ifdef __alpha
/*
* This is a fairly shameless hack to try to work around a bug that
* appears to exist when CSL is compiled using some releases of the
* C compiler that comes with DECs OSF on Alpha-based computers. I found
* experimentally that adding a function call here seemed to mend things.
* Originally this was a debug-print statement, but here I have just a
* dummy function call. The observed problem was the variable
* skipping_input not getting set properly, leading to shambles later on.
* ACN: August 1996
*/
dummy_function_call("ALPHA", skipping_input);
#endif
r = fastread();
skipping_input = 0;
errexit();
ch = Igetc();
fasl_byte_count++;
if (ch == EOF) return aerror("premature EOF in FASL file");
ch &= 0xff;
/* And drop through */
case F_DEF0: /* introduces defn of compiled code */
case F_DEF1:
case F_DEF2:
case F_DEF3:
case F_DEFN:
case F_DEFOPT:
case F_DEFHOPT:
case F_DEFREST:
case F_DEFHREST:
{ Lisp_Object name, bps, env;
push(r);
name = fastread();
pop(r);
errexit();
push(name);
if (qvalue(savedef) != nil)
{
if (just_reading_source)
{ Lisp_Object w;
#ifdef COMMON
w = get(name, loadsource_symbol, nil);
#else
w = get(name, loadsource_symbol);
#endif
if (w == nil &&
qvalue(loadsource_symbol) != nil) w = lisp_true;
if (w != nil)
{ Lisp_Object w1, chk = w;
CSLbool include = YES;
push3(chk, name, r);
if (consp(w))
{ if (integerp(qcar(w)))
{ chk = qcar(w);
w = list2star(qcar(w),
current_module, qcdr(w));
}
else w = cons(current_module, w);
}
else
{ if (integerp(w)) w = list2(w, current_module);
else w = ncons(current_module);
}
pop3(r, name, chk);
errexit();
/*
* If the load-source property is an integer then the source is only
* loaded if the definition concerned matched that as an MD5 checksum.
* (well actually I compute MD5 then truncate the digest to 60 bits).
* (I allow a property (integer ...) too).
* If load-source started off as just T then the last definition loaded
* will be the one that survives, but the load-source property will
* be replaced by a list of the modules that provided definitions (which
* may or may not be conflicting ones).
*/
if (integerp(chk) != nil && consp(r))
{ push4(name, r, chk, w);
w1 = Lmd60(nil, qcdr(r));
pop4(w, chk, r, name);
errexit();
push4(name, r, chk, w);
include = numeq2(w1, chk);
#ifdef DEBUG_FASL
prin_to_trace(name); trace_printf("\n");
prin_to_trace(r); trace_printf("\n");
prin_to_trace(w1); trace_printf("\n");
prin_to_trace(w); trace_printf("\n");
prin_to_trace(chk); trace_printf("\n");
trace_printf(" MD5 equality = %d\n", include);
#endif
pop4(w, chk, r, name);
errexit();
}
#ifdef DEBUG_FASL
else trace_printf("simple case\n");
#endif
if (include)
{ push2(name, r);
putprop(name, loadsource_symbol, w);
#ifdef DEBUG_FASL
trace_printf("record sourceloc\n");
#endif
pop2(r, name);
errexit();
#ifdef DEBUG_FASL
trace_printf("record savedef\n");
#endif
push2(name, r);
/* here I build up a list of the functions whose definitions were loaded */
w1 = cons(name, qvalue(work_symbol));
pop2(r, name);
errexit();
qvalue(work_symbol) = w1;
putprop(name, savedef, r);
}
}
}
else putprop(name, savedef, r);
errexit();
}
bps = fastread();
errexitn(1);
push(bps);
env = fastread();
errexitn(2);
pop(bps);
if (is_fixnum(bps))
{ int nn = int_of_fixnum(bps);
pop(name);
if (qvalue(savedef) != savedef)
{ switch (ch)
{
case F_DEF0: switch (nn)
{
case 0: set_fns(name, wrong_no_na, wrong_no_nb, f0_as_0);
break;
default:goto bad_tail;
}
break;
case F_DEF1: switch (nn)
{
case 0: set_fns(name, f1_as_0, too_many_1, wrong_no_1);
break;
case 1: set_fns(name, f1_as_1, too_many_1, wrong_no_1);
break;
default:goto bad_tail;
}
break;
case F_DEF2: switch (nn)
{
case 0: set_fns(name, too_few_2, f2_as_0, wrong_no_2);
break;
case 1: set_fns(name, too_few_2, f2_as_1, wrong_no_2);
break;
case 2: set_fns(name, too_few_2, f2_as_2, wrong_no_2);
break;
default:goto bad_tail;
}
break;
case F_DEF3: switch (nn)
{
case 0: set_fns(name, wrong_no_na, wrong_no_nb, f3_as_0);
break;
case 1: set_fns(name, wrong_no_na, wrong_no_nb, f3_as_1);
break;
case 2: set_fns(name, wrong_no_na, wrong_no_nb, f3_as_2);
break;
case 3: set_fns(name, wrong_no_na, wrong_no_nb, f3_as_3);
break;
default:goto bad_tail;
}
break;
case F_DEFN: switch (nn)
{
default:goto bad_tail;
}
break;
case F_DEFOPT:
switch (nn)
{
default:goto bad_tail;
}
break;
case F_DEFHOPT:
switch (nn)
{
default:goto bad_tail;
}
break;
case F_DEFREST:
switch (nn)
{
default:goto bad_tail;
}
break;
case F_DEFHREST:
switch (nn)
{
default:goto bad_tail;
}
break;
}
if ((qheader(name) & (SYM_C_DEF | SYM_CODEPTR)) ==
(SYM_C_DEF | SYM_CODEPTR))
{
#ifdef NOISY_RE_PROTECTED_FNS
if (verbos_flag & 2)
{ freshline_trace();
trace_printf("+++ Protected function ");
prin_to_trace(name);
trace_printf("\n");
}
#endif
}
else
{ qenv(name) = env;
if ((qheader(name) & SYM_C_DEF) != 0)
lose_C_def(name);
}
}
return nil;
bad_tail:
err_printf("+++++ Bad tailcall combination %d %d\n",
ch, nn);
return nil;
}
env = cons(bps, env);
pop(name);
errexit();
/*
* If the variable !*savedef has !*savedef as its value I will not instate
* function definitions here at all. This is a very odd thing to do, but
* turns out to help me save memory when I want to load FASL files in order
* to retrieve the Lisp form of definitions but I do not really want the
* code present instated.
*/
if (qvalue(savedef) != savedef)
{ switch (ch)
{
case F_DEF0: set_fns(name, wrong_no_0a, wrong_no_0b,
bytecoded0);
break;
case F_DEF1: set_fns(name, bytecoded1, too_many_1, wrong_no_1);
break;
case F_DEF2: set_fns(name, too_few_2, bytecoded2, wrong_no_2);
break;
case F_DEF3: set_fns(name, wrong_no_3a, wrong_no_3b,
bytecoded3);
break;
case F_DEFN: set_fns(name, wrong_no_na, wrong_no_nb, bytecodedn);
break;
case F_DEFOPT:
set_fns(name, byteopt1, byteopt2, byteoptn);
break;
case F_DEFHOPT:
set_fns(name, hardopt1, hardopt2, hardoptn);
break;
case F_DEFREST:
set_fns(name, byteoptrest1, byteoptrest2, byteoptrestn);
break;
case F_DEFHREST:
set_fns(name, hardoptrest1, hardoptrest2, hardoptrestn);
break;
}
if ((qheader(name) & (SYM_C_DEF | SYM_CODEPTR)) ==
(SYM_C_DEF | SYM_CODEPTR))
{
#ifdef NOISY_RE_PROTECTED_FNS
if (verbos_flag & 2)
{ freshline_trace();
trace_printf("+++ Protected function ");
prin_to_trace(name);
trace_printf("\n");
}
#endif
}
else
{ qenv(name) = env;
if ((qheader(name) & SYM_C_DEF) != 0) lose_C_def(name);
}
if (qvalue(comp_symbol) != nil &&
qfn1(native_symbol) != undefined1)
{ name = ncons(name);
nil = C_nil;
if (!exception_pending())
(qfn1(native_symbol))(qenv(native_symbol), name);
}
}
return nil;
}
case F_LS4:
push(r);
w = fastread();
pop(r);
errexit();
r = cons(w, r);
errexit();
/* DROP THROUGH */
case F_LS3:
push(r);
w = fastread();
pop(r);
errexit();
r = cons(w, r);
errexit();
/* DROP THROUGH */
case F_LS2:
push(r);
w = fastread();
pop(r);
errexit();
r = cons(w, r);
errexit();
/* DROP THROUGH */
case F_LS1:
push(r);
w = fastread();
pop(r);
errexit();
r = cons(w, r);
errexit();
return r;
case F_CHAR:
/*
* Note that in Kanji mode the interpretation here should be that the 16 bit
* character code is specified by bits/code. I ensure that when FASL files
* are written this arrangement holds.
*/
{ int32 bits, font, code;
bits = Igetc();
fasl_byte_count++;
if (bits == EOF) return aerror("premature EOF in FASL file");
font = Igetc();
fasl_byte_count++;
if (font == EOF) return aerror("premature EOF in FASL file");
code = Igetc();
fasl_byte_count++;
if (code == EOF) return aerror("premature EOF in FASL file");
return pack_char(bits, font & 0xff, code & 0xff);
}
case F_REP:
{ int c1, c2;
c1 = Igetc();
fasl_byte_count++;
if (c1 == EOF) return aerror("premature EOF in FASL file");
c2 = Igetc();
fasl_byte_count++;
if (c2 == EOF) return aerror("premature EOF in FASL file");
fp_rep = (c1 & 0xff) + ((c2 & 0xff) << 8);
ch = Igetc();
fasl_byte_count++;
if (ch == EOF) return aerror("premature EOF in FASL file");
ch &= 0xff;
continue;
}
#ifdef COMMON
case F_RAT:
w = fastread();
errexit();
push(w);
r = fastread();
pop(w);
errexit();
return make_ratio(w, r);
case F_CPX:
w = fastread();
errexit();
push(w);
r = fastread();
pop(w);
errexit();
return make_complex(w, r);
case F_FPS:
{ Lisp_Object w1;
if (Iread((char *)&w1, 4) != 4)
return aerror("FASL file corrupted");
fasl_byte_count += 4;
convert_fp_rep(&w1, fp_rep, current_fp_rep, 0);
return w1;
}
case F_FPF:
r = getvector(TAG_BOXFLOAT, TYPE_SINGLE_FLOAT,
sizeof(Single_Float));
errexit();
if (Iread((char *)r + CELL - TAG_BOXFLOAT, 4) != 4)
return aerror("FASL file corrupted");
fasl_byte_count += 4;
convert_fp_rep((char *)r + CELL - TAG_BOXFLOAT,
fp_rep, current_fp_rep, 1);
return r;
#endif
case F_FPD:
r = getvector(TAG_BOXFLOAT, TYPE_DOUBLE_FLOAT,
sizeof(Double_Float));
errexit();
*(int32 *)((char *)r + CELL - TAG_BOXFLOAT) = 0;
if (Iread((char *)r + 8 - TAG_BOXFLOAT, 8) != 8)
return aerror("FASL file corrupted");
fasl_byte_count += 8;
convert_fp_rep((char *)r + 8 - TAG_BOXFLOAT,
fp_rep, current_fp_rep, 2);
return r;
#ifdef COMMON
case F_FPL:
r = getvector(TAG_BOXFLOAT, TYPE_LONG_FLOAT, sizeof(Long_Float));
errexit();
if (Iread((char *)r + 8 - TAG_BOXFLOAT, 8) != 8)
return aerror("FASL file corrupted");
fasl_byte_count += 8;
/* Beware offset of 8 here if long floats -> 3 words */
convert_fp_rep((char *)r + 8 - TAG_BOXFLOAT,
fp_rep, current_fp_rep, 3);
return r;
#endif
case F_ID1:
case F_ID2:
case F_ID3:
case F_ID4:
case F_ID5:
case F_ID6:
case F_ID7:
case F_ID8:
case F_ID9:
case F_IDA:
case F_IDB:
case F_IDC:
case F_IDD:
case F_IDE:
case F_IDF:
operand = ch - F_ID1 + 1;
if (Iread(&boffo_char(0), operand) != operand)
return aerror("FASL file corrupted");
fasl_byte_count += operand;
if (skipping_input == 2) r = nil;
else r = iintern(boffo, operand, CP, 0);
errexit();
/*
* The KEEP_RECENT most recently used symbols are stored in a cyclic buffer
* so that if re-used they will be rapidly available.
*/
if (skipping_input == 0)
{ recent_pointer++;
if (recent_pointer == KEEP_RECENT) recent_pointer = 0;
w = elt(faslvec, recent_pointer);
#ifdef COMMON
if (qpackage(w) == nil)
#else
if (qheader(w) & SYM_ANY_GENSYM)
#endif
{ push(r);
#ifdef DEBUG_FASL
trace_printf("recording gensym ");
prin_to_trace(w);
trace_printf("\n");
#endif
w = cons(w, faslgensyms);
pop(r);
errexit();
faslgensyms = w;
}
elt(faslvec, recent_pointer) = r;
#ifdef DEBUG_FASL
trace_printf("recording ");
prin_to_trace(r);
trace_printf("\n");
#endif
}
return r;
case F_EXT: /* extend effective range of operand */
{ int ch1 = Igetc();
fasl_byte_count++;
if (ch1 == EOF) return aerror("premature EOF in FASL file");
operand = (operand << 8) + ((int32)ch1 & 0xff);
}
ch = (int32)Igetc();
fasl_byte_count++;
if (ch == EOF) return aerror("premature EOF in FASL file");
ch &= 0xff;
continue; /* dispatch again on next byte */
default:
return fastread1(ch, operand);
}
}
}
static char *trim_module_name(char *name, int32 *lenp)
{
int len = *lenp, len1;
len1 = len - 1;
/*
* Firstly I will decrease the length of the string if there is a "."
* towards the end.
*/
while (len1 > 0 && name[len1] != '.')
{ if (name[len1] == '/' || name[len1] == '\\')
{ len1 = len;
break;
}
len1--;
}
if (len1 > 0) len = len1;
/*
* Now I will try to remove any prefix that ends in "/" or "\".
* Through all this I will attempt to leave SOMETHING over from "silly"
* inputs such as ".....", but exactly what happens in such cases does not
* bother me much!
*/
len1 = len - 1;
while (len1 > 0 && name[len1] != '/' &&
name[len1] != '\\' && name[len1] != '.') len1--;
if (len1 > 0 && len1 < len-2)
{ len1++;
name += len1;
len -= len1;
}
*lenp = len;
return name;
}
Lisp_Object Lcopy_module(Lisp_Object nil, Lisp_Object file)
/*
* copy-module will ensure that the output PDS contains a copy of
* the module that is named. As a special case (copy-module nil) will
* copy the help data "module". There is no provision for copying
* startup banner data - that must be set up by hand.
*/
{
#ifdef DEMO_MODE
return onevalue(nil);
#else
Header h;
int32 len;
char *modname;
#ifdef SOCKETS
/*
* Security measure - remote client can not do "copy-module"
*/
if (socket_server != 0) return onevalue(nil);
#endif
if (file == nil) Icopy(NULL, 0);
else
{ if (symbolp(file))
{ file = get_pname(file);
errexit();
h = vechdr(file);
}
else if (!is_vector(file) ||
type_of_header(h = vechdr(file)) != TYPE_STRING)
return aerror("copy-module");
len = length_of_header(h) - CELL;
modname = (char *)file + CELL - TAG_VECTOR;
modname = trim_module_name(modname, &len);
Icopy(modname, (int)len);
}
return onevalue(nil);
#endif
}
Lisp_Object Ldelete_module(Lisp_Object nil, Lisp_Object file)
/*
* delete-module deletes the named module from the output PDS, supposing it
* was there to begin with. (delete-module nil) deletes any help data.
*/
{
#ifdef DEMO_MODE
return onevalue(nil);
#else
Header h;
int32 len;
char *modname;
#ifdef SOCKETS
/*
* Security measure - remote client can not do "delete-module"
*/
if (socket_server != 0) return onevalue(nil);
#endif
if (file == nil) Idelete(NULL, 0);
else
{ if (symbolp(file))
{ file = get_pname(file);
errexit();
h = vechdr(file);
}
else if (!is_vector(file) ||
type_of_header(h = vechdr(file)) != TYPE_STRING)
return aerror("delete-module");
len = length_of_header(h) - CELL;
modname = (char *)file + CELL - TAG_VECTOR;
modname = trim_module_name(modname, &len);
Idelete(modname, (int)len);
}
return onevalue(nil);
#endif /* DEMO_MODE */
}
Lisp_Object Lbanner(Lisp_Object nil, Lisp_Object info)
/*
* (banner nil) returns the current banner info (nil if none)
* (banner "string") sets new info
* (banner "") deletes any that there is.
*/
{
Header h;
int i;
int32 len;
char *name;
Ihandle save;
if (info == nil)
{ char b[64];
Icontext(&save);
if (Iopen_banner(0))
{ Irestore_context(save);
return onevalue(nil);
}
for (i=0; i<64; i++)
b[i] = (char)Igetc();
IcloseInput(NO);
Irestore_context(save);
info = make_string(b);
errexit();
return onevalue(info);
}
#ifdef DEMO_MODE
return onevalue(nil);
#else
#ifdef SOCKETS
/*
* Security measure - remote client can not change banner info
*/
if (socket_server != 0) return onevalue(nil);
#endif
if (symbolp(info))
{ info = get_pname(info);
errexit();
h = vechdr(info);
}
else if (!is_vector(info) ||
type_of_header(h = vechdr(info)) != TYPE_STRING)
return aerror("banner");
len = length_of_header(h) - CELL;
name = (char *)info + CELL - TAG_VECTOR;
if (len == 0) Iopen_banner(-2); /* delete banner info */
else
{ Icontext(&save);
if (Iopen_banner(-1))
{ Irestore_context(save);
return onevalue(nil);
}
if (len > 63) len = 63;
for (i=0; i<64; i++) Iputc(i >= len ? 0 : name[i]);
IcloseOutput();
Irestore_context(save);
}
return onevalue(lisp_true);
#endif /* DEMO_MODE */
}
Lisp_Object MS_CDECL Llist_modules(Lisp_Object nil, int nargs, ...)
/*
* display information about available modules
*/
{
argcheck(nargs, 0, "list-modules");
Ilist();
return onevalue(nil);
}
Lisp_Object Lwritable_libraryp(Lisp_Object nil, Lisp_Object file)
/*
* This tests if a library handle refers to a writable file.
*/
{
#ifdef DEMO_MODE
return onevalue(nil);
#else
int i;
directory *d;
if ((file & 0xffff) != SPID_LIBRARY) return onevalue(nil);
i = (file >> 20) & 0xfff;
d = fasl_files[i];
i = d->h.updated;
return onevalue(Lispify_predicate(i & D_WRITE_OK));
#endif
}
static Lisp_Object load_module(Lisp_Object nil, Lisp_Object file,
int sourceonly)
/*
* load_module() rebinds *package* in COMMON mode, but also note that
* it DOES rebind a whole load of variables so that loading one module
* can be done while in the process of loading another.
* also rebinds *echo to nil in case we are reading from a stream.
*/
{
char filename[LONGEST_LEGAL_FILENAME];
Header h;
int32 len;
Ihandle save;
Lisp_Object v;
CSLbool from_stream = NO;
int close_mode;
char *modname;
int32 save_recent = recent_pointer,
save_byte_count = fasl_byte_count;
#ifdef NAG
char *ptr;
int32 old_symbol_protect_flag;
#endif
if (is_stream(file)) h=0, from_stream = YES;
else if (symbolp(file))
{ file = get_pname(file);
errexit();
h = vechdr(file);
}
else if (!is_vector(file) ||
type_of_header(h = vechdr(file)) != TYPE_STRING)
return aerror("load-module");
current_module = file;
if (from_stream)
{ Icontext(&save);
if (Iopen_from_stdin())
{ err_printf("Failed to load module from stream\n");
Irestore_context(save);
return error(1, err_no_fasl, file);
}
push(qvalue(standard_input));
qvalue(standard_input) = file;
push(qvalue(echo_symbol));
qvalue(echo_symbol) = nil;
}
else
{ len = length_of_header(h) - CELL;
modname = (char *)file + CELL - TAG_VECTOR;
modname = trim_module_name(modname, &len);
Icontext(&save);
if (Iopen(modname, (int)len, YES, filename))
{ err_printf("Failed to find \"%s\"\n", filename);
Irestore_context(save);
return error(1, err_no_fasl, file);
}
}
v = getvector_init(CELL*(KEEP_RECENT+1), nil);
nil = C_nil;
if (exception_pending())
{ IcloseInput(NO);
Irestore_context(save);
if (from_stream)
{ flip_exception();
pop(qvalue(echo_symbol));
pop(qvalue(standard_input));
flip_exception();
}
return nil;
}
push(qvalue(work_symbol));
qvalue(work_symbol) = nil; /* list of functions loaded in source form */
/*
* I will account time spent fast-loading things as "storage management"
* overhead to be counted as "garbage collector time" rather than
* regular "cpu time"
*/
push_clock();
if (verbos_flag & 2)
{ freshline_trace();
if (sourceonly)
{ if (from_stream) trace_printf("Loading source from a stream\n");
else trace_printf("Loading source for \"%s\"\n", filename);
}
else
{ if (from_stream) trace_printf("Fast-loading from a stream\n");
else trace_printf("Fast-loading \"%s\"\n", filename);
}
}
#ifdef NAG
/*
* This next bit is designed to ensure that, under the default configuration,
* the user can overwrite bits of the system that are re-defined in the kernel,
* but loading the Lisp versions from a "standard" image file will have no
* effect. This is totally AXIOM dependent!
*/
old_symbol_protect_flag = symbol_protect_flag;
ptr = strrchr(filename, '/');
/* /* BEWARE for Axiom purposes!!!!!!!! */
if (ptr && strlen(ptr) > 5 && strncmp(ptr+1,"axiom",5) == 0)
symbol_protect_flag = 1;
#endif
push(CP);
push(faslvec);
faslvec = v;
push(faslgensyms);
faslgensyms = nil;
push(qvalue(savedef));
if (sourceonly) qvalue(savedef) = savedef;
just_reading_source = sourceonly;
recent_pointer = 0;
fasl_byte_count = 0;
skipping_input = 0;
for (;;)
{ Lisp_Object r = fastread();
nil = C_nil;
if (exception_pending() || r == CHAR_EOF) break;
#ifdef DEBUG_FASL
trace_printf("FASL: ");
loop_print_trace(r);
trace_printf("\n");
#endif
if (!sourceonly) voideval(r, nil);
nil = C_nil;
if (exception_pending()) break;
}
close_mode = YES;
if (exception_pending()) flip_exception(), close_mode = NO;
pop(qvalue(savedef));
pop(faslgensyms);
pop(faslvec);
pop(CP);
if (sourceonly) file = qvalue(work_symbol);
else file = nil;
pop(qvalue(work_symbol));
/* If something already smashed there is no joy in checking the checksum */
push(file);
IcloseInput(close_mode);
Irestore_context(save);
#ifdef NAG
symbol_protect_flag = old_symbol_protect_flag;
#endif
pop(file);
if (from_stream)
{ pop(qvalue(echo_symbol));
pop(qvalue(standard_input));
}
recent_pointer = save_recent;
fasl_byte_count = save_byte_count;
gc_time += pop_clock();
if (!close_mode)
{ flip_exception();
return nil;
}
return onevalue(file);
}
Lisp_Object Lload_source(Lisp_Object nil, Lisp_Object file)
{
return load_module(nil, file, 1);
}
Lisp_Object Lload_module(Lisp_Object nil, Lisp_Object file)
{
return load_module(nil, file, 0);
}
#ifdef DEBUG_FASL
static void IputcDebug(int c, int line)
{
Iputc(c);
trace_printf("Iputc(%d/%x/%s: %d %.8x %.8x)\n", c, c, fasl_code(c),
line, C_stack, C_nil);
}
#define Iputc(c) IputcDebug(c, __LINE__)
#endif
#ifndef DEMO_MODE
static void out_fasl_prefix(int32 n)
/*
* Used to generate any prefixes to cope with large operands in
* FASL streams
*/
{
if (n != 0)
{ out_fasl_prefix(n >> 8);
Iputc(F_EXT);
Iputc((int)(n & 0xff));
}
}
#endif
Lisp_Object Lmodule_exists(Lisp_Object nil, Lisp_Object file)
{
char filename[LONGEST_LEGAL_FILENAME], tt[32];
Header h;
int32 len;
int32 size;
char *modname;
if (symbolp(file))
{ file = get_pname(file);
errexit();
h = vechdr(file);
}
else if (!is_vector(file) ||
type_of_header(h = vechdr(file)) != TYPE_STRING)
return aerror("modulep");
len = length_of_header(h) - CELL;
modname = (char *)file + CELL - TAG_VECTOR;
modname = trim_module_name(modname, &len);
if (Imodulep(modname, (int)len, tt, &size, filename))
return onevalue(nil);
tt[24] = 0;
file = make_string(tt);
errexit();
return onevalue(file);
}
Lisp_Object Lstart_module(Lisp_Object nil, Lisp_Object name)
/*
* This must be called before write-module - it resets the table of recently-
* mentioned identifiers to be empty. Calling with a nil argument
* closes the current fasl file, otherwise the arg is the name of
* a file to open. It is not intended that ordinary programmers call
* this function - it is for use from within the compiler.
* As a special bit of magic the name passed can be a Lisp stream, in
* which case the module data will be written to it.
*/
{
#ifdef DEMO_MODE
return onevalue(nil);
#else
Lisp_Object w;
#ifdef SOCKETS
/*
* Security measure - remote client can not do "FASLOUT" & start-module
*/
if (socket_server != 0) return onevalue(nil);
#endif
recent_pointer = 0;
skipping_output = 0;
fp_rep_set = NO;
if (name == nil)
{ if (fasl_output_file)
{ int k = (int)Ioutsize() & 0x3;
/*
* Here I arrange that all FASL modules will end up being a multiple of
* 4 bytes long. "WHY?" Well I once suffered from a machine that was not
* very good at supporting odd-length data transfers (the suggestion I
* collected is that it MAY be because I had an early version of an 80386 CPU
* chip installed). The padding up here is not very painful and may avoid
* some painful trouble on my machine (and hence maybe on some other ones).
* The machine concerned is a PC and the chip and 80386, just in case you
* wondered. Zortech technical support were very helpful trying to
* track down the crashes I was having - even had they provided a software
* work-around in their code at some time I should leave this code and comment
* in CSL.
* Note (June 1992) I now have a computer with a newer CPU chip in it and
* the problem mentioned above does not arise - but it still seems reasonable
* to keep modules a multiple of 4 bytes long.
* Note (October 1995) Well, now I have a Pentium rather than a 386, and
* my previous 80486 system has gone down the feeding chain to replace the
* old and dodgy 80386. So sometime within the next year or so I will
* remove this comment, but still leave modules padded to multiples of
* 4 bytes since maybe I would introduce more bugs removing that than I would
* save.
* (January 1999) This little essay continues to entertain me. The 386 system
* happens to be around Cambridge again as a "relic" having been discarded as
* too old-fashioned and slow by pretty well everybody! Gosh how machines
* change during the life-time of a piece of software!
* (march 2001) "early 386" bug hah. Gosh that was slow by today's standards.
*/
while (k != 3) k++, Iputc(F_NIL);
Iputc(F_END);
IcloseOutput();
faslvec = nil;
faslgensyms = nil;
fasl_output_file = NO;
fasl_stream = nil;
if (verbos_flag & 2)
{ freshline_trace();
#ifdef COMMON
trace_printf(";; FASLEND: hits = %ld, misses = %ld\n",
(long)hits, (long)misses);
#else
trace_printf("+++ FASLEND: hits = %ld, misses = %ld\n",
(long)hits, (long)misses);
#endif
}
return onevalue(lisp_true);
}
else return onevalue(nil);
}
else if (is_stream(name))
{ push(name);
w = getvector_init(CELL*(KEEP_RECENT+1), nil);
pop(name);
errexit();
faslvec = w;
hits = misses = 0;
faslgensyms = nil;
fasl_stream = name;
fasl_output_file = YES;
Iopen_to_stdout(); /* initialises checksum calculation */
return onevalue(lisp_true);
}
else
{ char filename[LONGEST_LEGAL_FILENAME];
char *modname;
int32 len;
Header h;
push(name);
w = getvector_init(CELL*(KEEP_RECENT+1), nil);
pop(name);
errexit();
faslvec = w;
hits = misses = 0;
faslgensyms = nil;
#ifdef COMMON
if (complex_stringp(name))
{ name = simplify_string(name);
errexit();
h = vechdr(name);
}
else
#endif
if (symbolp(name))
{ name = get_pname(name);
errexit();
h = vechdr(name);
}
else if (!(is_vector(name))) return aerror("start-module");
else if (type_of_header(h = vechdr(name)) != TYPE_STRING)
return aerror("start-module");
len = length_of_header(h) - CELL;
modname = (char *)name + CELL - TAG_VECTOR;
/*
* Here I will play jolly games! The name as passed in to start-module will
* be allowed to be a fairly general file-name. If there is a suffix of the
* form ".xxx" on the end I will strip that off. If there is a directory-
* style component before that (as signalled by having a "/" or a "\" or
* another "." within the name) I will trim that off too. So the input
* string "/home/xxx/something.fsl" (say) would be treated exactly as if
* it had been just "something".
*/
modname = trim_module_name(modname, &len);
if (len >= sizeof(filename)) len = sizeof(filename);
if (Iopen(modname, (int)len, NO, filename))
{ err_printf("Failed to open \"%s\"\n", filename);
return onevalue(nil);
}
fasl_output_file = YES;
return onevalue(lisp_true);
}
#endif /* DEMO_MODE */
}
Lisp_Object Ldefine_in_module(Lisp_Object nil, Lisp_Object a)
{
#ifdef DEMO_MODE
return onevalue(nil);
#else
int32 args, opts, ntail;
#ifdef SOCKETS
/*
* Security measure - remote client can not do "define-in-module"
*/
if (socket_server != 0) return onevalue(nil);
#endif
if (!is_fixnum(a)) return aerror("define-in-module");
if (a == fixnum_of_int(-1))
{ Iputc(F_SDEF);
/*
* An expression preceeded with F_SDEF will be loaded again only if
* the variable "*savedef" is true at the time of loading, or if
* the load-source function is called and the function whose definition
* is involved has a load-source property.
*/
skipping_output = 1;
return onevalue(nil);
}
skipping_output = 0;
args = int_of_fixnum(a);
opts = args >> 8;
ntail = opts >> 10;
if (ntail != 0)
return aerror("tailcall magic not supported in FASL files yet");
opts &= 0x3ff;
if (opts == 0) switch (args & 0xff)
{
case 0: Iputc(F_DEF0);
break;
case 1: Iputc(F_DEF1);
break;
case 2: Iputc(F_DEF2);
break;
case 3: Iputc(F_DEF3);
break;
default:Iputc(F_DEFN);
break;
}
else switch (opts >> 8)
{
default:
case 0: Iputc(F_DEFOPT);
break;
case 1: Iputc(F_DEFHOPT);
break;
case 2: Iputc(F_DEFREST);
break;
case 3: Iputc(F_DEFHREST);
break;
}
return onevalue(nil);
#endif /* DEMO_MODE */
}
#ifdef DEBUG_FASL
static void IwriteDebug(char *x, int n, int line)
{
int i;
Iwrite(x, n);
trace_printf("Iwrite %d %.8x %.8x", line, C_nil, C_stack);
for (i=0; i<n ;i++)
{ trace_printf(" %d/%x", x[i], x[i]);
if (32 <= x[i] && x[i] < 0x7f) trace_printf("/'%c'", x[i]);
}
trace_printf("\n");
}
#define Iwrite(x, n) IwriteDebug(x, n, __LINE__)
#endif
#ifndef DEMO_MODE
static Lisp_Object write_module0(Lisp_Object nil, Lisp_Object a);
static Lisp_Object write_module1(Lisp_Object a)
{
Lisp_Object nil = C_nil;
if (is_bfloat(a))
{ Header h = flthdr(a);
if (!fp_rep_set)
{ fp_rep_set = YES;
Iputc(F_REP);
Iputc(current_fp_rep & 0xff);
Iputc((current_fp_rep >> 8) & 0xff);
}
switch (type_of_header(h))
{
default:
return aerror("unrecognized FP number type");
#ifdef COMMON
case TYPE_SINGLE_FLOAT:
Iputc(F_FPF);
Iwrite((char *)a + CELL - TAG_BOXFLOAT, 4);
break;
#endif
case TYPE_DOUBLE_FLOAT:
Iputc(F_FPD);
/* nb offset here is 8 in both 32 and 64 bit modes */
Iwrite((char *)a + 8L - TAG_BOXFLOAT, 8);
break;
#ifdef COMMON
case TYPE_LONG_FLOAT:
Iputc(F_FPL);
Iwrite((char *)a + 8 - TAG_BOXFLOAT, 8);
break;
#endif
}
}
else if (is_char(a))
{ Iputc(F_CHAR);
/*
* Note that for somewhat dubious reasons I have separated out the
* end of file character earlier on and treated it oddly.
*/
Iputc((int)bits_of_char(a));
Iputc((int)font_of_char(a));
Iputc((int)code_of_char(a));
}
else if (is_bps(a))
{ char *d = data_of_bps(a);
int32 len = length_of_header(*(Header *)(d - CELL)) - CELL;
switch (len >> 8)
{
case 3: Iputc(F_BP3);
break;
case 2: Iputc(F_BP2);
break;
case 1: Iputc(F_BP1);
break;
default:
out_fasl_prefix(len >> 8);
Iputc(F_BP0);
break;
}
Iputc((int)(len & 0xff));
Iwrite(d, len);
}
else if (is_vector(a))
{ Header h = vechdr(a);
int32 len = length_of_header(h) - CELL, i;
switch (type_of_header(h))
{
case TYPE_STRING:
out_fasl_prefix(len >> 8);
Iputc(F_STR);
Iputc((int)(len & 0xff));
Iwrite((char *)a + CELL - TAG_VECTOR, len);
break;
case TYPE_HASH: /* Writing these may be easy... */
case TYPE_SIMPLE_VEC:
case TYPE_STRUCTURE:
len /= CELL;
out_fasl_prefix(len >> 8);
Iputc(type_of_header(h) == TYPE_HASH ? F_HASH :
type_of_header(h) == TYPE_STRUCTURE ? F_STRUCT : F_VEC);
Iputc((int)(len & 0xff));
for (i=0; i<len; i++)
{ push(a);
write_module0(nil, elt(a, i));
pop(a);
errexit();
}
break;
default:
/*
* The explicit enumeration of left-over cases is here ready for when
* (or if!) I ever decide to extend the FASL format to support these
* extra types. Until I do please note that Common Lisp arrays and
* bit-vectors can not be coped with here.
*/
#ifdef COMMON
case TYPE_ARRAY:
case TYPE_BITVEC1:
case TYPE_BITVEC2:
case TYPE_BITVEC3:
case TYPE_BITVEC4:
case TYPE_BITVEC5:
case TYPE_BITVEC6:
case TYPE_BITVEC7:
case TYPE_BITVEC8:
#endif
case TYPE_MIXED1:
case TYPE_MIXED2:
return aerror("vector type unsupported by write-module");
}
}
else return aerror("write-module");
return nil;
}
#endif /* DEMO_MODE */
Lisp_Object Lwrite_module(Lisp_Object nil, Lisp_Object a)
{
#ifdef DEBUG_FASL
push(a);
trace_printf("FASLOUT: ");
loop_print_trace(a);
errexit();
trace_printf("\n");
pop(a);
#endif
return write_module0(nil, a);
}
static Lisp_Object write_module0(Lisp_Object nil, Lisp_Object a)
/*
* write one expression to the currently selected output stream.
* That stream ought to have been opened using start-module, and is
* binary (i.e. no record separators or concern about record length
* must intrude).
*/
{
#ifdef DEMO_MODE
return onevalue(nil);
#else
#ifdef SOCKETS
/*
* Security measure - remote client can not do "write-module"
*/
if (socket_server != 0) return onevalue(nil);
#endif
if (a == nil) Iputc(F_NIL);
else if (a == lisp_true) Iputc(F_TRU);
else if (a == CHAR_EOF) Iputc(F_END);
/*
* In Common Lisp mode there will be a certain amount of horrible fun with
* symbols and the package system. But a symbol that is EQ to one recently
* processed can be handled that way regardless.
*/
else if (is_symbol(a))
{ int32 i, len;
Lisp_Object w, w1;
int pkgid = 0;
int32 k;
#ifdef COMMON
int32 lenp;
#endif
for (i=0; i<KEEP_RECENT; i++)
{ int32 w = recent_pointer - i;
if (w < 0) w += KEEP_RECENT;
if (a == elt(faslvec, w))
{ Iputc((int)(F_OLD+i));
hits++;
return onevalue(nil);
}
}
push(a);
w = get_pname(a);
pop(a);
errexit();
/*
* The FASL mechanism does not in general preserve EQness. In particular
* cyclic structures will upset it, and multiple references to the same
* string or float (etc) will read back as distinct entities. However
* within one S-expression I will arrange that uninterned symbols are
* handled tolerably cleanly... The first time such a symbol is written
* its name is dumped in the file. When this is read back a new uninterned
* symbol with that name is created. Usually the next few uses will use
* the "recently referenced symbol" mechanism, and so will refer back to
* this value. For gensyms I extend the usual cyclic buffer that holds the
* recently mentioned symbols with a fall-back list of mentioned gensyms,
* and refer into that using F_EXT followed by a "recent" reference. This
* mechanism gets activated especially if the FASL file contains a
* macro-expanded but not compiled form where the expansion introduces
* gensyms as labels etc.
*/
#ifdef COMMON
/*
* The code here is expected to match that in print.c. It sets pkgid to
* indicate how the symbol involved needs to be put into the FASL file.
* My byte format there is optimised for the case where no package marker
* is needed. The values of pkgid are:
* 0 no package marker needed
* 1 display as #:xxx (ie as a gensym)
* 2 display as :xxx (ie in keyword package)
* 3 display as ppp:xxx (external in its home package)
* 4 display as ppp::xxx (internal in its home package)
*/
if (qpackage(a) == nil)
{ for (w1 = faslgensyms, k=0; w1!=nil; w1=qcdr(w1), k++)
{ if (qcar(w1) == a)
{ out_fasl_prefix(1 + (k>>7));
Iputc((int)(F_OLD+(k & 0x7f)));
#ifdef DEBUG_FASL
trace_printf("++ Ancient FASL gensym ref %d\n", k);
#endif
hits++;
return onevalue(nil);
}
}
pkgid = 1; /* gensym */
}
else if (qpackage(a) == qvalue(keyword_package)) pkgid = 2;
else if (qpackage(a) == CP) pkgid = 0; /* home is current */
else
{ pkgid = 3;
k = packflags_(CP);
if (k != 0 && k <= 10)
{ k = ((int32)1) << (k+SYM_IN_PKG_SHIFT-1);
if (k & qheader(a)) pkgid = 0;
}
else k = 0;
if (pkgid != 0)
{ push2(a, w);
w1 = Lfind_symbol_1(nil, w);
pop2(w, a);
errexit();
if (mv_2 != nil && w1 == a)
{ pkgid = 0;
qheader(a) |= k;
}
else if (qheader(a) & SYM_EXTERN_IN_HOME) pkgid = 3;
else pkgid = 4;
}
}
misses++;
if (skipping_output == 0 || pkgid == 1)
{ recent_pointer++;
if (recent_pointer == KEEP_RECENT) recent_pointer = 0;
w1 = elt(faslvec, recent_pointer);
if (qpackage(w1) == nil)
{ push(a);
#ifdef DEBUG_FASL
trace_printf("recording gensym ");
prin_to_trace(w1);
trace_printf("\n");
#endif
w1 = cons(w1, faslgensyms);
pop(a);
errexit();
faslgensyms = w1;
}
elt(faslvec, recent_pointer) = a;
#ifdef DEBUG_FASL
trace_printf("recording ");
prin_to_trace(a);
trace_printf("\n");
#endif
}
len = length_of_header(vechdr(w)) - CELL;
switch (pkgid)
{
case 0: if (1 <= len && len <= 15) Iputc(F_ID1 + (int)len - 1);
else
{ out_fasl_prefix(len >> 8);
Iputc(F_SYM);
Iputc((int)(len & 0xff));
}
lenp = -1;
break;
case 1: out_fasl_prefix(len >> 8);
Iputc(F_PKGINT);
Iputc(0);
lenp = 0;
break;
case 2: out_fasl_prefix(len >> 8);
Iputc(F_PKGEXT);
Iputc(0);
lenp = 0;
break;
case 3: out_fasl_prefix(len >> 8);
Iputc(F_PKGEXT);
lenp = 1;
break;
case 4: out_fasl_prefix(len >> 8);
Iputc(F_PKGINT);
lenp = 1;
break;
}
if (lenp > 0)
{ push(w);
a = packname_(qpackage(a));
pop(w);
errexit();
lenp = length_of_header(vechdr(a)) - CELL;
/*
* Another ugliness rears its head here... I allow for symbols that have
* very long names, but I will only support packages where the name of the
* package is less then 256 characters. This is so I can use a one-byte
* counter to indicate its length. If I REALLY have to I can put in
* support for ultra-long names for packages, but the mess involved
* seems offensive at the moment. I truncate any over-long package name
* at 255 here. Silently.
*/
if (lenp > 255) lenp = 255;
Iputc(lenp);
Iputc((int)(len & 255));
Iwrite((char *)a + CELL - TAG_VECTOR, lenp);
}
else if (lenp == 0) Iputc((int)(len & 0xff));
Iwrite((char *)w + CELL - TAG_VECTOR, len);
#else
/*
* In Standard Lisp mode things that were gensyms in the original
* will probably get read back in as ordinary symbols. This at least
* ensures that multiple references to the same gensym end up matching, and
* it is less effort than the Common Lisp solution...
* Actually I am now finding this to be UNSATISFACTORY and am going to
* change it to be much more like the behaviour I have in the COMMON case.
*/
if ((qheader(a) & SYM_ANY_GENSYM) != 0)
{ for (w1 = faslgensyms, k=0; w1!=nil; w1=qcdr(w1), k++)
{ if (qcar(w1) == a)
{ out_fasl_prefix(1 + (k>>7));
Iputc((int)(F_OLD+(k & 0x7f)));
#ifdef DEBUG_FASL
trace_printf("++ Ancient FASL gensym ref %d\n", k);
#endif
hits++;
return onevalue(nil);
}
}
pkgid = 1; /* gensym */
}
misses++;
/*
* See comment where F_GENSYM is read to understand why gensyms must be
* recorded even when skipping...
*/
if (skipping_output == 0 || pkgid == 1)
{ recent_pointer++;
if (recent_pointer == KEEP_RECENT) recent_pointer = 0;
w1 = elt(faslvec, recent_pointer);
if ((qheader(w1) & SYM_ANY_GENSYM) != 0)
{ push(a);
#ifdef DEBUG_FASL
trace_printf("recording gensym ");
prin_to_trace(w1);
trace_printf("\n");
#endif
w1 = cons(w1, faslgensyms);
pop(a);
errexit();
faslgensyms = w1;
}
elt(faslvec, recent_pointer) = a;
#ifdef DEBUG_FASL
trace_printf("recording ");
prin_to_trace(a);
trace_printf("\n");
#endif
}
len = length_of_header(vechdr(w)) - CELL;
if (pkgid == 0)
{ if (1 <= len && len <= 15) Iputc(F_ID1 + (int)len - 1);
else
{ out_fasl_prefix(len >> 8);
Iputc(F_SYM);
Iputc((int)(len & 0xff));
}
}
else
{ out_fasl_prefix(len >> 8); /* here it is a gensym */
Iputc(F_GENSYM);
Iputc((int)(len & 0xff));
}
Iwrite((char *)w + CELL - TAG_VECTOR, len);
#endif
}
else if (is_cons(a))
{ int32 len, i;
Lisp_Object cara = qcar(a), cdra = qcdr(a);
if (cara == quote_symbol && consp(cdra) && qcdr(cdra) == nil)
{ Iputc(F_QUT);
return write_module0(nil, qcar(cdra));
}
len = 1;
while (consp(cdra)) len++, cdra = qcdr(cdra);
out_fasl_prefix(len >> 8);
if (cdra == nil)
{ switch (len)
{
case 1:
Iputc(F_LS1);
break;
case 2:
Iputc(F_LS2);
break;
case 3:
Iputc(F_LS3);
break;
case 4:
Iputc(F_LS4);
break;
default:
Iputc(F_LST);
Iputc((int)(len & 0xff));
break;
}
}
else
{ Iputc(F_DOT);
Iputc((int)(len & 0xff));
push(a);
stackcheck1(1, cdra);
write_module0(nil, cdra);
pop(a);
errexit();
}
cdra = nil;
for (i=0; i<len; i++)
{ push(a);
cdra = cons(qcar(a), cdra);
pop(a);
errexit();
a = qcdr(a);
}
for (i=0; i<len; i++)
{ push(cdra);
write_module0(nil, qcar(cdra));
pop(cdra);
errexit();
cdra = qcdr(cdra);
}
}
else if (is_fixnum(a))
{ int32 n = int_of_fixnum(a);
CSLbool sign;
/*
* The fixnum range is 0xf8000000 to 0x07ffffff
*/
if (n < 0) n = -n, sign = YES;
else sign = NO;
out_fasl_prefix(n >> 8);
Iputc(sign ? F_NEG : F_INT);
Iputc((int)(n & 0xff));
}
else if (is_numbers(a))
{ Header h = numhdr(a);
int32 len, i;
switch (type_of_header(h))
{
default:
return aerror("unrecognized number type");
#ifdef COMMON
case TYPE_RATNUM:
Iputc(F_RAT);
break;
case TYPE_COMPLEX_NUM:
Iputc(F_CPX);
break;
#endif
case TYPE_BIGNUM:
len = length_of_header(h) - CELL;
out_fasl_prefix(len >> 8);
Iputc(F_BIG);
Iputc((int)(len & 0xff));
/*
* I write out the value byte by byte so that the binary in the file
* does not depend on the byte-ordering used by the host computer.
*/
for (i=0; i<len; i+=4) /* always in 32-bit units */
{ unsigned32 v =
*(unsigned32 *)((char *)a + CELL - TAG_NUMBERS + i);
Iputc((int)(v >> 24) & 0xff);
Iputc((int)(v >> 16) & 0xff);
Iputc((int)(v >> 8) & 0xff);
Iputc((int)v & 0xff);
}
return onevalue(nil);
}
#ifdef COMMON
write_module0(nil, *(Lisp_Object *)((char *)a + CELL - TAG_NUMBERS));
errexit();
return write_module0(nil,
*(Lisp_Object *)((char *)a + 2*CELL - TAG_NUMBERS));
#endif
}
#ifdef COMMON
else if (is_sfloat(a))
{ Lisp_Object w = a;
/*
* I write out floating point values in whatever the natural host
* representation is - but prefix the first FP value with a marker that
* identifies what that representation is so that when the file is re-loaded
* a conversion can be applied as necessary.
*/
if (!fp_rep_set)
{ fp_rep_set = YES;
Iputc(F_REP);
Iputc(current_fp_rep & 0xff);
Iputc((current_fp_rep >> 8) & 0xff);
}
Iputc(F_FPS);
Iwrite((char *)&w, 4);
}
#endif
else write_module1(a);
return onevalue(nil);
#endif /* DEMO_MODE */
}
/*
* (set-help-file "key" "path") puts an extra help file on the cwin
* HELP menu. If "path" is NIL then the item specified by "key" is
* removed. If "key" is NIL then all user-inserted items are removed.
*/
Lisp_Object Lset_help_file(Lisp_Object nil, Lisp_Object a, Lisp_Object b)
{
#ifdef CWIN
char *w, *aa, *bb = NULL;
int32 lena, lenb;
if (a != nil)
{ w = get_string_data(a, "set-help-file", &lena);
errexit();
aa = (char *)malloc(lena+1);
if (aa == NULL) return aerror("set-help-file");
memcpy(aa, w, lena);
aa[lena] = 0;
}
else
{ aa = NULL;
b = nil;
}
if (b != nil)
{ w = get_string_data(b, "set-help-file", &lenb);
errexit();
bb = (char *)malloc(lenb+1);
if (bb == NULL) return aerror("set-help-file");
memcpy(bb, w, lenb);
bb[lenb] = 0;
}
cwin_set_help_file(aa, bb);
#endif
return onevalue(nil);
}
#ifdef NO_HELP_SYSTEM
#undef HELP_SYSTEM
#else
#ifndef HELP_SYSTEM
# define HELP_SYSTEM 1 /* Always enabled, now */
#endif
#endif
#ifdef HELP_SYSTEM
/*
* write-help-module (now) takes as argument a file-name. It expects the
* file to be in INFO format. It copies the text from the file into
* a section of the image file and builds an index (which will remain in
* memory).
*/
/*
* write-help-module has two arguments here because the previous version did
* and changing that would cause short-term confusion...
*/
#ifndef DEMO_MODE
static void merge_sort(char *a, char *b, int left, int right)
{
int next = left+8, mid, i, j;
if (left==right) return; /* Empty vector to sort */
while (next < right && a[next] != 0) next += 8;
if (next >= right) return; /* Only one item there */
mid = ((left+right)/2) & ~7;
if (mid <= next) mid = next;
else while (a[mid] != 0) mid -= 8;
/*
* Now (left..mid) is non-empty because mid >= next, and (mid..right) is not
* empty because mid rounded downwards and the vector has at least two
* items in it.
*/
merge_sort(a, b, left, mid);
merge_sort(a, b, mid, right);
for (i=left; i<=right; i++) b[i] = a[i];
i = left; j = mid; next = left;
/* Now merge back from b to a */
while (i < mid && j < right)
{ int i1 = i+4, j1=j+4, k;
for (k=0; k<28; k++)
{ if (b[i1] != b[j1]) break;
i1++;
j1++;
}
if (b[i1] <= b[j1])
{ do
{ *(int32 *)(&a[next]) = *(int32 *)(&b[i]);
*(int32 *)(&a[next+4]) = *(int32 *)(&b[i+4]);
next += 8;
i += 8;
} while (b[i] != 0);
}
else
{ do
{ *(int32 *)(&a[next]) = *(int32 *)(&b[j]);
*(int32 *)(&a[next+4]) = *(int32 *)(&b[j+4]);
next += 8;
j += 8;
} while (b[j] != 0);
}
}
while (i < mid) a[next++] = b[i++];
while (j < right) a[next++] = b[j++];
}
/*
* To get some sort of compression on the help text I will collect
* statistics about which pairs of characters occur adjacent to one
* another. I will first use an array of 256*256 unsigned characters. When
* a particular pair records 255 in this count field I will enter it in
* an overflow hash table. The space for each of these tables will be
* grabbed using malloc(), so if you try to build a help database on
* a machine where grabbing an extra 100K of memory is awkward then you
* may be out of luck.
*/
typedef struct char_pair_hash
{
int32 count;
char c1, c2;
} char_pair_hash;
/*
* I observe (having done the experiment) that the REDUCE help database
* causes overflow for somewhat under 400 character-pairs. Thus a hash
* table with room for twice that number should suffice for now. Note that
* an utterly worst-case file would have to be over 256Kbytes long for
* more than 1000 character pairs each to occur over 256 times, and all
* realistic text files will be a very long way from that case. If, by
* mistake, one fed this code a file that was already compressed it would
* collapse with an overfull hash table. Tough luck - in such cases I will
* just deliver slightly silly results.
*/
#define OVERFLOW_SIZE 1000
#define PASS_COUNT 12
static int MS_CDECL compare_char_counts(void const *aa, void const *bb)
{
return ((char_pair_hash *)bb)->count -
((char_pair_hash *)aa)->count;
}
#define INFO_CHAR ('_' & 0x1f)
#endif /* DEMO_MODE */
Lisp_Object Lwrite_help_module(Lisp_Object nil,
Lisp_Object name, Lisp_Object ignore)
{
#ifdef DEMO_MODE
return onevalue(nil);
#else
int i, c1, c2, c3, pass, linep;
int32 info_seen;
unsigned char cx1[256], cx2[256];
char buff[16], line[256];
/*
* There can be no more than 256 items put in the coded[] hash table, and
* in general I expect it to be considerably less than that. So having the
* table of size 409 (a prime) guarantees it will never get too full so
* performance ought to be pretty good.
*/
#define CODED_SIZE 409
char_pair_hash coded[CODED_SIZE];
int32 buffp;
Ihandle save;
Lisp_Object v = nil, v1;
int32 indexlength = -10000, saving;
int32 helpsize = 0, len;
char filename[LONGEST_LEGAL_FILENAME];
Header h;
FILE *file;
unsigned char *frequencies;
char_pair_hash *overflow;
CSL_IGNORE(ignore);
#ifdef SOCKETS
/*
* Security measure - remote client can not do write-help-module"
*/
if (socket_server != 0) return onevalue(nil);
#endif
#ifdef COMMON
if (complex_stringp(name))
{ name = simplify_string(name);
errexit();
h = vechdr(name);
}
else
#endif
if (symbolp(name))
{ name = get_pname(name);
errexit();
h = vechdr(name);
}
else if (!(is_vector(name))) return aerror("write-help-module");
else if (type_of_header(h = vechdr(name)) != TYPE_STRING)
return aerror("write-help-module");
len = length_of_header(h) - CELL;
if (len > sizeof(filename)) len = sizeof(filename);
file = open_file(filename, (char *)name + (CELL-TAG_VECTOR),
(size_t)len, "r", NULL);
if (file == NULL) return aerror("write-help-module");
Icontext(&save);
if (Iopen_help(-1)) /* Open help sub-file for writing */
{ Irestore_context(save);
fclose(file);
return aerror("Unable to open help file");
}
for (i=0; i<CODED_SIZE; i++)
{ coded[i].c1 = coded[i].c2 = 0;
coded[i].count = 0;
}
frequencies = (unsigned char *)malloc(0x10000);
overflow = (char_pair_hash *)malloc(OVERFLOW_SIZE*sizeof(char_pair_hash));
if (frequencies == NULL || overflow == NULL)
{ Irestore_context(save);
fclose(file);
free((void *)frequencies);
free((void *)overflow);
return aerror("Not enough memory to build help database");
}
for (i=0; i<256; i++) cx1[i] = cx2[i] = 0;
for (pass=1; pass<=PASS_COUNT; pass++)
{ term_printf("Start of pass %d\n", pass);
if (pass == PASS_COUNT)
{ v = getvector(TAG_VECTOR, TYPE_STRING, CELL+4+indexlength);
nil = C_nil;
/*
* I will get another vectors the same size so that I have plenty of
* space for a simple-minded implementation of merge-sort.
*/
if (!exception_pending())
{ push(v);
v1 = getvector(TAG_VECTOR, TYPE_STRING, CELL+4+indexlength);
pop(v);
nil = C_nil;
}
else v1 = nil;
if (exception_pending())
{ flip_exception();
IcloseOutput();
Irestore_context(save);
fclose(file);
free((void *)frequencies);
free((void *)overflow);
flip_exception();
return nil;
}
}
indexlength = 512;
fseek(file, SEEK_SET, 0L);
for (i=0; i<0x10000; i++) frequencies[i] = 0;
for (i=0; i<OVERFLOW_SIZE; i++)
{ overflow[i].c1 = overflow[i].c2 = 0;
overflow[i].count = 0;
}
for (i=0; i<16; i++) buff[i] = 0;
buffp = 0;
i = 100;
saving = 0;
/* An "info" file has a little header at the top - skip that */
while ((c2 = getc(file)) != EOF &&
c2 != INFO_CHAR) /* do nothing */;
c2 = getc(file); /* newline following the ^_ */
linep = 0;
info_seen = 0;
while ((c2 = getc(file)) != EOF)
{ unsigned32 x;
int n;
if (c2 == '\n')
{ line[linep] = 0;
if (linep == 1 && line[0] == INFO_CHAR)
{ int32 bp = buffp;
/*
* I flush the compression look-ahead buffer when I find a "^_" record
* so that the break between help topics is on a real byte boundary and so
* that I can tell where in the help file this boundary will fall.
*/
for (;;)
{ bp++;
c1 = buff[bp & 15];
buff[bp & 15] = 0;
if (c1 == 0) break;
if (pass == PASS_COUNT)
{ if (c1 == INFO_CHAR) Iputc(0);
else Iputc(c1);
helpsize++;
}
}
info_seen = helpsize;
linep = 0;
continue; /* Throws away the '\n' after '^_' */
}
else if (info_seen >= 0)
{ if (strcmp(line, "Tag Table:") == 0) break;
/*
* Here I must spot "File:" lines and count the size of the node name and/or
* insert it in the index vector.
*/
if (strncmp(line, "File: ", 6) == 0)
{ linep = linep-6;
while (linep>0 &&
strncmp(&line[linep], "Node: ", 6) != 0)
linep--;
if (linep != 0)
{ char *node = &line[linep+6];
int nodelen = 0;
/*
* I will force node labels into upper case here. I use upper rather than
* lower case mainly because it turns out to make it easier for me to compare
* the sorted order of my key-table with the order imposed by a (DOS) sort
* utility I have. In particular it makes the collating order of '_' with
* letters compatible with the independent external utility.
*/
while (node[nodelen] != ',' &&
node[nodelen] != 0)
{ node[nodelen] = (char)toupper(node[nodelen]);
nodelen++;
}
if (nodelen > 28) nodelen = 28;
if (pass == PASS_COUNT)
{ ucelt(v, indexlength++) = 0;
ucelt(v, indexlength++) = (unsigned char)(info_seen & 0xff);
ucelt(v, indexlength++) = (unsigned char)((info_seen >> 8) & 0xff);
ucelt(v, indexlength++) = (unsigned char)((info_seen >> 16) & 0xff);
#ifdef DEBUG_HELP_SYSTEM
term_printf("Node(%.*s) position %d\n",
nodelen, node, info_seen);
#endif
while (nodelen-- != 0)
celt(v, indexlength++) = *node++;
while (indexlength & 7)
celt(v, indexlength++) = 0;
}
else indexlength = indexlength +
((nodelen + 11) & ~7);
}
}
info_seen = -1;
}
else info_seen = -1;
linep = 0;
}
else if (linep < 255) line[linep++] = (char)c2;
/*
* I truncate lines at 255 characters. This is not so comfortable as all that!
* The Reduce Help Database ends up with lines of up to 195 characters long,
* in cases where the names of several adjacent sections are all ridiculously
* long.
*/
cx1[c2] = (unsigned char)c2;
for (;;)
{ c3 = buff[(buffp-1) & 15];
if (c3 != 0)
{ int c4 = 0;
int32 hash = ((((c3 & 0xff)<<8)+
(c2 & 0xff))*32359) % CODED_SIZE;
for (;;)
{ if (coded[hash].count == 0) break;
else if (coded[hash].c1 == c3 &&
coded[hash].c2 == c2)
{ c4 = coded[hash].count;
buffp--;
buff[buffp & 15] = 0;
saving++;
break;
}
hash++;
if (hash == CODED_SIZE) hash = 0;
}
if (c4 != 0)
{ c2 = c4;
continue;
}
}
break;
}
c1 = buff[(buffp+1) & 15];
c3 = buff[(buffp+2) & 15];
buff[buffp & 15] = (char)c2;
buffp++;
buff[buffp & 15] = 0;
c2 = c3;
if (c1 == 0 || c2 == 0 || c1 == INFO_CHAR ||
c2 == INFO_CHAR) continue;
if (pass == PASS_COUNT)
{ if (c1 == INFO_CHAR) Iputc(0); /* terminate a section */
else Iputc(c1);
helpsize++;
}
x = ((c1 & 0xff) << 8) | (c2 & 0xff);
n = frequencies[x];
if (--i == 0)
{ stackcheck0(0);
i = 100;
}
if (n == 255)
{ x = (x*32359) % OVERFLOW_SIZE;
/*
* In general I expect inserting character-pairs in this table will only
* take a few probes. But any scan that takes over 3*OVERFLOW_SIZE/4 is
* abandoned. The effect is that worst-case behaviour could eventually
* fill the table up totally, so this long-stop would be the only thing
* preventing the code from looping for ever. So then it would run around
* 200 times slower than usual, but it would eventually finish! Such bad cases
* can not happen with reasonable input data.
*/
for (n=0;n<(3*OVERFLOW_SIZE)/4;n++)
{ if (overflow[x].count == 0)
{ overflow[x].c1 = (char)c1;
overflow[x].c2 = (char)c2;
overflow[x].count = 256;
break;
}
else if (c1 == overflow[x].c1 &&
c2 == overflow[x].c2)
{ overflow[x].count++;
break;
}
x = x + 1;
if (x == OVERFLOW_SIZE) x = 0;
}
}
else frequencies[x] = (unsigned char)(n+1);
}
/*
* It is possible (probable!) that at the end of processing there are a few
* characters left buffered up. Flush them out now.
*/
if (pass == PASS_COUNT)
{ for (;;)
{ buffp++;
c1 = buff[buffp & 15];
buff[buffp & 15] = 0;
if (c1 == INFO_CHAR) Iputc(0);
else Iputc(c1);
helpsize++;
if (c1 == 0) break; /* NB I write a zero to terminate */
}
}
term_printf("Saving this pass was %d\n", saving);
qsort(overflow, (size_t)OVERFLOW_SIZE, sizeof(char_pair_hash),
compare_char_counts);
if (pass < PASS_COUNT)
{ for (i=0; i<(pass==PASS_COUNT-1 ? OVERFLOW_SIZE : 10); i++)
{ int rep;
int32 hash;
if (overflow[i].c1 == 0 || overflow[i].c2 == 0) continue;
for (rep=1; rep<256; rep++)
if (cx1[rep]==0) break;
if (rep == 256) break;
c1 = overflow[i].c1;
c2 = overflow[i].c2;
cx1[rep] = (unsigned char)c1;
cx2[rep] = (unsigned char)c2;
hash = ((((c1 & 0xff)<<8)+(c2 & 0xff))*32359) % CODED_SIZE;
for (;;)
{ if (coded[hash].count == 0)
{ coded[hash].c1 = (char)c1;
coded[hash].c2 = (char)c2;
coded[hash].count = rep;
break;
}
else if (coded[hash].c1 == c1 &&
coded[hash].c2 == c2) break;
hash++;
if (hash == CODED_SIZE) hash = 0;
}
term_printf("%.2x %.2x => %.2x (%d)\n",
c1 & 0xff, c2 & 0xff, rep & 0xff, overflow[i].count);
}
}
}
celt(v, indexlength) = 0; /* needed as a terminator */
for (i=0; i<256; i++)
{ celt(v, 2*i) = cx1[i];
celt(v, 2*i+1) = cx2[i];
}
i = Ioutsize() & 3;
while ((i & 3) != 0) Iputc(0), i++; /* Pad to multiple of 4 bytes */
IcloseOutput();
fclose(file);
free((void *)frequencies);
free((void *)overflow);
trace_printf("%ld bytes of help data\n", (long)helpsize);
Irestore_context(save);
/*
* Now I have made a help module and an associated index vector, however
* the index information is at present unordered. I want to sort it but
* the situation is a little curious - the items in the vector are of
* variable length and so most of the sorting methods I can think of
* are not easily applied. I guess that merge-sort is the solution...
*/
merge_sort(&celt(v, 0), &celt(v1, 0), 512, indexlength);
#ifdef DEBUG_HELP_SYSTEM
/* Now, mainly as a debugging measure, I display the sorted index */
term_printf("\nSorted index\n");
i = 512;
while (i < indexlength)
{ for (len=4; len<32; len++)
{ c1 = celt(v, i+len);
if (c1 == 0) break;
term_printf("%c", c1);
}
for (;(len&7)!=0; len++) term_printf(" ");
buffp = ucelt(v, i+3) & 0xff;
buffp = (buffp << 8) + (ucelt(v, i+2) & 0xff);
buffp = (buffp << 8) + (ucelt(v, i+1) & 0xff);
i += len;
for (;len<36; len++) term_printf(" ");
term_printf("%7d\n", buffp);
}
#endif
help_index = v; /* Only set up the index vector if all seemed OK */
return onevalue(nil);
#endif /* DEMO_MODE */
}
/*
* Here I will have a simulation of some modest part of the "curses"
* interface that Unix tends to support. I will certainly not support
* everything - just a minimum that I think I need for my help browser.
* I support the following environments
* (a) Watcom C for DOS, using the Watcom graphics library
* (b) Unix using real "curses", but adding two new functions initkb()
* and resetkb() to switch to unbuffered un-echoed input from getch()
* (c) Watcom C and Windows (win32) using a separate 25 by 80 window
* for all the text output here. This case will be flagged by having
* the pre-processor symbol WINDOWS_NT defined.
*/
#include <ctype.h>
#ifdef WINDOWS_NT
/*
* Under win32 I will have the implementation of all this stuff as
* part of my window manager code, and hence elsewhere. So I just provide
* a collection of declarations to show what will be available.
*/
/*
* For Windows I will only support an 80 by 25 window. I guess it
* would be easy enough to permit other sizes, except that I do not have
* an easy answer to what should happen if the user re-sizes the window
* while other things are going on. Hence my conservative caution - at
* least for now!
*/
extern int LINES, COLS;
/* initscr() must be called once at the start of a run */
extern void initscr(void);
/*
* initkb() and resetkb() delimit regions in the code where keyboard
* input is treated as requests to the curses window but is accepted
* with no delay and no echo. Also mouse events can be posted during
* this time.
*/
extern void initkb(void);
extern void resetkb(void);
extern int mouse_button; /* set non-zero when user presses a button */
extern int mouse_cx; /* 0 <= mouse_cx < COLS */
extern int mouse_cy; /* 0 <= mouse_cy < LINES */
/* refresh() is called to force the screen to be up to date */
extern void refresh();
/* endwin() hides the curses window, restoring simple text handling */
extern void endwin(void);
/* Move text insertion point. Origin (0,0) is top left of screen */
extern void move(int y, int x);
/* standout() and standend() delimit inverse video (or whatever) text */
extern void standout(void);
extern void standend(void);
/* erase() clears the whole screen */
extern void erase(void);
/*
* addch() and addstr() add text to the screen, advancing the cursor. I
* view it as illegal to write beyond either right or bottom margin of the
* screen.
*/
extern void addch(int ch);
extern void addstr(char *s);
/*
* getch() reads a character from the keyboard. It does not wait for
* a newline, and does not echo anything. Because the name getch() may be
* in use in some C libraries in a way that could conflict I use some
* re-naming here. If there has been a mouse-click recently then getch()
* should return a value (0x100 + bits) where the odd bits may indicate which
* button was pressed. In that case (mouse_cx,mouse_cy) will be the
* character-position coordinates at which the hit was taken. Systems
* that can not support a mouse do not have to worry about this and can always
* return a value in the range 0..255, or EOF. On some systems getch() will
* return 0 with no delay if there is no character available (so that
* the application will busy-wait). On others it is entitled to wait until
* the user presses a key. But (once again) it should not do line editing or
* wait for an ENTER.
*/
extern int my_getch(void);
#undef getch
#define getch() my_getch()
#else /* WINDOWS_NT */
#ifdef __WATCOMC__
/*
* Here I view __WATCOMC__ as flagging an implementation using MSDOS,
* and in this context I will take that to mean DOS/4GW
*/
#include <dos.h>
#include <i86.h>
#include <graph.h>
int LINES=0, COLS=0;
int XPIXELS=0, YPIXELS=0;
static int in_curses_mode = 0;
/*
* I seem to observe that when I run this in a DOS box under Windows
* (Windows 95 at least) in a DOS window then the first time I try my
* DOS window is expanded to full screen. If I shrink it back with
* ALT-ENTER then subsequent runs of a program that uses initsrc() do not
* seem to mazimize the window. This seems a little odd!
*/
void initscr()
{
struct videoconfig vinfo;
_getvideoconfig(&vinfo);
COLS = vinfo.numtextcols;
LINES = vinfo.numtextrows;
XPIXELS = vinfo.numxpixels;
/* In text screen-modes I expect all characters to be 8x8 */
if (XPIXELS == 0) XPIXELS = 8*COLS;
YPIXELS = vinfo.numypixels;
if (YPIXELS == 0) YPIXELS = 8*LINES;
_settextposition(1, 1);
in_curses_mode = 1;
}
int mouse_cx = 0;
int mouse_cy = 0;
static int mouse_button = 0;
#pragma off (check_stack)
void _loadds far mouse_click_handler (int max, int mcx, int mdx)
{
#pragma aux mouse_click_handler parm [EAX] [ECX] [EDX]
mouse_cx = (COLS*mcx)/XPIXELS;
mouse_cy = (LINES*mdx)/YPIXELS;
mouse_button = max & 0xe;
}
#pragma on (check_stack)
void initkb()
{
struct SREGS sregs;
union REGS inregs, outregs;
int far *ptr;
int (far *function_ptr)();
segread(&sregs);
/* check for mouse driver */
inregs.w.ax = 0;
int386(0x33, &inregs, &outregs);
if (outregs.w.ax == -1)
{ /* show mouse cursor */
inregs.w.ax = 0x1;
int386(0x33, &inregs, &outregs);
/* install click watcher */
inregs.w.ax = 0xC;
inregs.w.cx = 0x0002 + 0x0008;
function_ptr = mouse_click_handler;
inregs.x.edx = FP_OFF(function_ptr);
sregs.es = FP_SEG(function_ptr);
int386x(0x33, &inregs, &outregs, &sregs);
mouse_button = 0;
}
}
void resetkb()
{
/* check installation again (to clear watcher) */
union REGS inregs, outregs;
inregs.w.ax = 0;
int386(0x33, &inregs, &outregs);
mouse_button = 0;
}
/*
* In this implementation I will reflect changes to the display
* instantly, so refresh() [which curses needs] will be a no-op,
* except that if I had left curses mode re-entering it should clear
* the screen.
*/
void refresh()
{
if (!in_curses_mode)
{ _clearscreen(_GWINDOW);
_settextposition(1, 1);
in_curses_mode |= 1;
}
}
void endwin()
{
_settextposition(LINES, 1);
in_curses_mode = 0;
}
void move(int y, int x)
{
_settextposition(y+1, x+1);
}
void standout()
{
_settextcolor(0);
_setbkcolor(7);
}
void standend()
{
_settextcolor(7);
_setbkcolor(0);
}
void erase()
{
_clearscreen(_GWINDOW);
_settextposition(1, 1);
in_curses_mode = 1;
}
void addch(int ch)
{
char b[4];
b[0] = ch;
b[1] = 0;
_outtext(b);
}
void addstr(char *s)
{
_outtext(s);
}
int my_getch()
{
if (mouse_button != 0)
{ int w = mouse_button;
mouse_button = 0;
return 0x100 + w;
}
if (!kbhit()) return 0;
return getch();
}
#undef getch
#define getch() my_getch()
#else /* __WATCOMC__ */
/*
* Assume Unix here - or some system providing Unix compatibility.
* Note that "curses" may not always be installed, but is needed here
* if the embedded help system is to work.
*/
#include <curses.h>
/*
* In fact for the curses-Unix style interface I do not support a mouse,
* but that is no great problem - I just let mouse_button remain zero
* always.
*/
int mouse_button = 0; /* set non-zero when user presses a button */
int mouse_cx = 0; /* 0 <= mouse_cx < COLS */
int mouse_cy = 0; /* 0 <= mouse_cy < LINES */
void initkb()
{
cbreak();
noecho();
}
void resetkb()
{
nocbreak();
echo();
}
#endif /* __WATCOMC__ */
#endif /* WINDOWS_NT */
/*
* End of curses compatibility code
*/
char file[256], node[256], next[256], prev[256], up[256];
long int topic_start = 0, topic_header_size = 0;
void find_word(char *buffer, char *tag, char *value)
{
int len = strlen(tag), ch;
*value = 0;
while (*buffer != 0)
{ if (strncmp(buffer, tag, len) != 0)
{ buffer++;
continue;
}
buffer += len;
while ((ch = *buffer) == ' ' && ch != 0) buffer++;
if (ch == 0) return;
while ((ch = *buffer++) != ',' && ch != 0) *value++ = (char)ch;
*value = 0;
return;
}
}
static int shown_lines = 0;
static unsigned char cstack[28];
static int cstackp;
/*
* I have here some fairly simple compression on the help text. Characters
* can either stand for themselves or for pairs of characters. The table in
* the first 512 bytes of the index table indicates which. If at location
* (2*i, 2*i+1) this table contains (p,q) then q=0 means that the character
* i stands for itself (and p=i). Otherwise i expands to p followed by q where
* each of these are subject to the same potential expansion. Code 0 is
* reserved as a section or file terminator.
*/
static int getc_help(void)
{
Lisp_Object nil = C_nil;
Lisp_Object v = help_index;
unsigned char *p;
int k, c2;
CSL_IGNORE(nil);
p = &ucelt(v, 0);
if (cstackp == 0) k = Igetc();
else k = cstack[--cstackp];
for (;;)
{ if (k == EOF || k == 0) return 0;
c2 = p[2*k+1];
if (c2 == 0) return k;
cstack[cstackp++] = (unsigned char)c2;
k = p[2*k];
}
}
#define MAX_MENUS 32
static int at_end_of_topic = 0;
static int menu_line[MAX_MENUS], menu_col[MAX_MENUS], max_menu, active_menu;
static char menu_text[MAX_MENUS][40];
void display_next_page(void)
{
int ch, line = 0, col, llen = 80, i, j;
char buffer[256];
if (COLS < 80) llen = COLS;
erase();
at_end_of_topic = 0;
max_menu = active_menu = -1;
/*
* There is an "ugly" here. The sprintf that formats the header line
* does not protect against over-long topic-names that could lead to over-full
* buffers. I make the buffer 256 characters long and hope! I force a '\0'
* in at column 80 (or whatever) later on to effect truncation.
*/
sprintf(buffer, "Node: %s, Next: %s, Prev: %s, Up:%s",
node, next, prev, up);
buffer[llen] = 0;
move(0, 0);
addstr(buffer);
while (++line < LINES)
{ col = 0;
while ((ch = getc_help()) != '\n')
{ if (ch == 0 || ch == EOF)
{ at_end_of_topic = 1;
break;
}
if (col < llen) buffer[col++] = (char)ch;
}
if (at_end_of_topic) break;
buffer[col] = 0;
for (i=0; i<col &&
!(buffer[i]=='*' &&
buffer[i+1]==' '); i++);
for (j=i+1; j<col &&
!(buffer[j]==':' &&
buffer[j+1]==':'); j++);
if (j < col && max_menu < MAX_MENUS-2)
{ max_menu++;
menu_line[max_menu] = line;
menu_col[max_menu] = i + 2;
memset(menu_text[max_menu], 0, 39);
strncpy(menu_text[max_menu], &buffer[i+2], j-i-2);
menu_text[max_menu][39] = 0;
}
move(line, 0);
addstr(buffer);
shown_lines++;
}
refresh();
}
void skip_some_lines(int n)
{
int ch, line = 0, col;
char buffer[16];
at_end_of_topic = 0;
while (++line <= n)
{ col = 0;
while ((ch = getc_help()) != '\n')
{ if (ch == 0 || ch == EOF)
{ at_end_of_topic = 1;
break;
}
if (col < 8) buffer[col++] = (char)ch;
}
if (at_end_of_topic) break;
shown_lines++;
}
}
static int topic_in_index(char *key)
{
int len = strlen(key);
Lisp_Object nil = C_nil;
Lisp_Object v = help_index;
int32 size, i, low, high, offset;
int k, l;
char *p;
CSL_IGNORE(nil);
if (len > 28) len = 28;
if (!is_vector(v)) return 0;
size = length_of_header(vechdr(v)) - CELL;
p = &celt(v, 0);
/*
* The first 512 bytes of the help index contain data for the decompression
* process, and so are not used in the following search.
* I stop at size-4 on the next line because I added an extra 4 bytes
* of padding on the end of the help index to terminate the last entry.
*/
low = 512;
high = size-4;
/*
* Do a binary search a bit, but when I am down to a fairly narrow
* range drop down to linear scan. Note that binary search is somewhat
* curious given that the items in my index are variable length!
*/
while (high > low + 64) /* largest item in table is 28 bytes */
{ int32 mid = (high + low)/2;
mid &= ~7; /* Align it properly */
/*
* At this stage mid might point part way through an index entry. Move it
* up until it points at something that has a zero first byte. Because
* I started off with low and high well separated this is guaranteed to
* terminate with mid strictly between low and high. I slide up rather
* than down to (slightly) balance the rounding down that happened in
* the original calculation of the mid-point.
*/
while (p[mid] != 0) mid += 8;
#ifdef DEBUG_HELP_SYSTEM
term_printf("Compare %.*s with %s\n", len, key, &p[mid+4]);
#endif
for (k=0; k<len && toupper(key[k]) == p[mid+k+4]; k++) {};
if (k < len)
{ if (toupper(key[k]) < p[mid+k+4]) high = mid;
else low = mid;
continue;
}
else if (p[mid+k+4] != 0)
{ high = mid;
continue;
}
low = high = mid; /* Found it exactly */
break;
}
l = 0;
for (i=low; i<high; i=i+l+4)
{ l = 4;
while (p[i+l+4] != 0) l += 8;
if (len > l) continue;
for (k=0; k<len && toupper(key[k]) == p[i+k+4]; k++) {};
if (k < len) continue;
if (p[i+len+4] != 0) continue;
l = 0; /* Match found: mark the fact with l=0 */
break;
}
if (l != 0) return 0; /* Failed to find the key */
offset = p[i+3] & 0xff;
offset = (offset << 8) + (p[i+2] & 0xff);
offset = (offset << 8) + (p[i+1] & 0xff);
IcloseInput(NO);
if (Iopen_help(offset)) return 0;
topic_start = offset;
cstackp = 0;
return 1;
}
int find_topic(char *s)
{
char buffer[256];
int i, c1;
#ifdef DEBUG_HELP_SYSTEM
term_printf("Find-topic \"%s\"\n", s);
#endif
if (!topic_in_index(s)) return 0;
#ifdef DEBUG_HELP_SYSTEM
term_printf("Found in index at %d\n", topic_start);
#endif
shown_lines = 0;
cstackp = 0;
for (i=0, c1=getc_help();c1!='\n';c1=getc_help())
if (i < 250) buffer[i++] = (char)c1;
buffer[i] = 0;
topic_header_size = i;
find_word(buffer, "Node:", node);
find_word(buffer, "File:", file);
find_word(buffer, "Next:", next);
find_word(buffer, "Prev:", prev);
find_word(buffer, "Up:", up);
#ifdef DEBUG_HELP_SYSTEM
term_printf("%s:%s:%s:%s:%s\n", node, file, next, prev, up);
#endif
display_next_page();
return 1;
}
void restart_topic(void)
{
IcloseInput(NO);
if (!Iopen_help(topic_start))
{ int i;
for (i=0; i<topic_header_size; i++) getc_help();
}
cstackp = 0;
}
static void help_about_help_browser(void)
{
int ch;
erase();
move( 1, 0); addstr("*** HELP BROWSER COMMANDS ***");
move( 3, 0); addstr("b go Back to start of topic");
move( 4, 0); addstr("space move on one page through topic");
move( 5, 0); addstr("delete move back one page in topic");
move( 6, 0); addstr("?, h display this Help text");
move( 7, 0); addstr("n go to Next topic");
move( 8, 0); addstr("p go to Previous topic");
move( 9, 0); addstr("u go Up a level");
move(10, 0); addstr("q Quit");
move(11, 0); addstr("tab, m Select next Menu item");
move(12, 0); addstr("ENTER, f Follow selected menu item");
move(13, 0); addstr("1-9 First 9 menu items visible");
move(15, 0); addstr("[Type SPACE or ENTER to continue]");
refresh();
while ((ch = getch()) != ' ' && ch != '\n' && ch != '\r');
}
static int help_main(char *s)
{
int i, w;
initscr();
initkb();
if (!find_topic(s)) return 1;
for (;;)
{ w = getch();
switch (tolower(w))
{
case 'q': break;
case 'n': if (next[0] != 0)
{ if (!find_topic(next)) goto redisplay_current_topic;
}
continue;
case 'p': if (prev[0] != 0)
{ if (!find_topic(prev)) goto redisplay_current_topic;
}
continue;
case 'u': if (up[0] != 0)
{ if (!find_topic(up)) goto redisplay_current_topic;
}
continue;
case ' ': if (!at_end_of_topic) display_next_page();
continue;
case 0x8:
case 0x7f:
case 0xff:
if (shown_lines <= (LINES-2)) continue;
i = shown_lines - 2*LINES + 2;
if (i < 0) i = 0;
restart_topic();
shown_lines = 0;
skip_some_lines(i);
display_next_page();
continue;
case '?':
case 'h': help_about_help_browser();
/* Drop through */
redisplay_current_topic:
case 'b': restart_topic();
shown_lines = 0;
display_next_page();
continue;
case '\t':
case 'm': /* For this version I make "m" skip to the next menu item */
if (max_menu < 0) continue;
if (active_menu >= 0)
{ move(menu_line[active_menu], menu_col[active_menu]);
addstr(menu_text[active_menu]);
active_menu++;
if (active_menu > max_menu) active_menu = 0;
}
else active_menu = 0;
move(menu_line[active_menu], menu_col[active_menu]);
standout();
addstr(menu_text[active_menu]);
standend();
refresh();
continue;
case '\n': /* Follow a menu item, as selected */
case '\r':
case 'f': if (max_menu >= 0 && active_menu >= 0)
{ if (!find_topic(menu_text[active_menu]))
goto redisplay_current_topic;
}
continue;
case '1': case '2': case '3': case '4': case '5':
case '6': case '7': case '8': case '9':
w = w - '1';
if (w <= max_menu)
{ if (!find_topic(menu_text[w]))
goto redisplay_current_topic;
}
continue;
default: continue;
}
break;
}
resetkb();
endwin();
return 0;
}
static void help(char *word, int len)
{
Ihandle save;
char key[32];
Icontext(&save);
if (Iopen_help(0)) debug_printf("\nNo heap available\n");
else
{ if (len > 28) len = 28;
key[len] = 0;
while (--len >= 0) key[len] = word[len];
/* memcpy(key, word, len); <curses.h> on a sparc kills this!! */
/* key[len] = 0; by its attempts to mix BSD & sysV. */
if (help_main(key)) debug_printf("\nNo help available\n");
IcloseInput(NO);
}
Irestore_context(save);
return;
}
Lisp_Object lisp_help(Lisp_Object nil, Lisp_Object a)
{
switch ((int)a & TAG_BITS)
{
case TAG_SYMBOL:
#ifndef COMMON
if (a == nil)
{ help("Top", 3); /* this tag is the default one to give */
return onevalue(nil);
}
#endif
a = get_pname(a);
errexit();
case TAG_VECTOR:
if (type_of_header(vechdr(a)) == TYPE_STRING)
{ Header h = vechdr(a);
int32 len = length_of_header(h); /* counts in bytes */
len -= CELL;
help(&celt(a, 0), len);
return onevalue(nil);
}
case TAG_CONS:
#ifdef COMMON
if (a == nil)
{ help("Top", 3);
return onevalue(nil);
}
#endif
while (consp(a))
{ push(a);
lisp_help(nil, qcar(a));
pop(a);
errexit();
a = qcdr(a);
}
return onevalue(nil);
case TAG_BOXFLOAT:
default:
return onevalue(nil);
}
}
Lisp_Object Lhelp(Lisp_Object nil, Lisp_Object a)
{
return lisp_help(nil, a);
}
Lisp_Object Lhelp_2(Lisp_Object nil, Lisp_Object a, Lisp_Object b)
{
push(b);
lisp_help(nil, a);
pop(b);
errexit();
return lisp_help(nil, b);
}
Lisp_Object MS_CDECL Lhelp_n(Lisp_Object nil, int nargs, ...)
{
if (nargs == 0) help("Top", 0);
else
{ va_list a;
int i;
va_start(a, nargs);
push_args(a, nargs);
for (i=0; i<nargs; i++)
{ Lisp_Object c = stack[i-nargs+1];
lisp_help(nil, c);
errexitn(nargs);
}
popv(nargs);
}
return onevalue(nil);
}
#else
Lisp_Object Lwrite_help_module(Lisp_Object nil, Lisp_Object a, Lisp_Object b)
{
return onevalue(nil);
}
Lisp_Object Lhelp(Lisp_Object nil, Lisp_Object a)
{
term_printf("HELP not built in to this version of the system\n");
return onevalue(nil);
}
Lisp_Object Lhelp_2(Lisp_Object nil, Lisp_Object a, Lisp_Object b)
{
return Lhelp(nil, a);
}
Lisp_Object MS_CDECL Lhelp_n(Lisp_Object nil, int nargs, ...)
{
return Lhelp(nil, nil);
}
#endif /* HELP_SYSTEM */
char prompt_string[32];
Lisp_Object Lsetpchar(Lisp_Object nil, Lisp_Object a)
{
Lisp_Object old = prompt_thing;
CSL_IGNORE(nil);
prompt_thing = a;
#define escape_nolinebreak 0x80
escaped_printing = escape_nolinebreak;
set_stream_write_fn(lisp_work_stream, count_character);
memory_print_buffer[0] = 0;
set_stream_write_other(lisp_work_stream, write_action_list);
stream_char_pos(lisp_work_stream) = 0;
active_stream = lisp_work_stream;
push(old);
#ifdef DEMO_MODE
{ char *s = "DemoRed";
while (*s != 0) count_character(*s++, lisp_work_stream);
}
#endif
internal_prin(a, 0);
pop(old);
errexit();
#ifdef CWIN
cwin_set_prompt(memory_print_buffer);
#endif
memcpy(prompt_string, memory_print_buffer, 32);
prompt_string[31] = 0;
return onevalue(old);
}
/* end of fasl.c */