1 /*@z33.c:Database Service:OldCrossDb(), NewCrossDb(), SymToNum()@*************/
2 /*                                                                           */
3 /*  THE LOUT DOCUMENT FORMATTING SYSTEM (VERSION 3.39)                       */
4 /*  COPYRIGHT (C) 1991, 2008 Jeffrey H. Kingston                             */
5 /*                                                                           */
6 /*  Jeffrey H. Kingston (jeff@it.usyd.edu.au)                                */
7 /*  School of Information Technologies                                       */
8 /*  The University of Sydney 2006                                            */
9 /*  AUSTRALIA                                                                */
10 /*                                                                           */
11 /*  This program is free software; you can redistribute it and/or modify     */
12 /*  it under the terms of the GNU General Public License as published by     */
13 /*  the Free Software Foundation; either Version 3, or (at your option)      */
14 /*  any later version.                                                       */
15 /*                                                                           */
16 /*  This program is distributed in the hope that it will be useful,          */
17 /*  but WITHOUT ANY WARRANTY; without even the implied warranty of           */
18 /*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            */
19 /*  GNU General Public License for more details.                             */
20 /*                                                                           */
21 /*  You should have received a copy of the GNU General Public License        */
22 /*  along with this program; if not, write to the Free Software              */
23 /*  Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA   */
24 /*                                                                           */
25 /*  FILE:         z33.c                                                      */
26 /*  MODULE:       Database Service                                           */
27 /*  EXTERNS:      OldCrossDb, NewCrossDb, DbCreate(), DbInsert(),            */
28 /*                DbConvert(), DbClose(), DbLoad(), DbRetrieve(),            */
29 /*                DbRetrieveNext()                                           */
30 /*                                                                           */
31 /*****************************************************************************/
32 #define INIT_DBCHECK_NUM	107
33 #include "externs.h"
34 
35 
36 /*****************************************************************************/
37 /*                                                                           */
38 /*  DBCHECK_TABLE                                                            */
39 /*                                                                           */
40 /*  A symbol table holding all non-galley cross references, basically        */
41 /*  implementing a function (sym, tag) -> fpos (if any).                     */
42 /*                                                                           */
43 /*     dtab_new(newsize)                New empty table, newsize capacity    */
44 /*     dtab_insert(x, S)                Insert new (sym, tag) pair x into S  */
45 /*     dtab_retrieve(sym, tag, S)       Retrieve (sym, tag) pair from S      */
46 /*     dtab_debug(S, fp)                Debug print of table S to file fp    */
47 /*                                                                           */
48 /*****************************************************************************/
49 
50 typedef struct
51 { int dbchecktab_size;				/* size of table             */
52   int dbchecktab_count;				/* number of objects held    */
53   OBJECT dbchecktab_item[1];
54 } *DBCHECK_TABLE;
55 
56 #define	dtab_size(S)	(S)->dbchecktab_size
57 #define	dtab_count(S)	(S)->dbchecktab_count
58 #define	dtab_item(S, i)	(S)->dbchecktab_item[i]
59 
60 #define hash(pos, sym, tag, S)						\
61 { FULL_CHAR *p = tag;							\
62   pos = (unsigned long) sym;						\
63   while( *p ) pos += *p++;						\
64   pos = pos % dtab_size(S);						\
65 }
66 
dtab_new(int newsize)67 static DBCHECK_TABLE dtab_new(int newsize)
68 { DBCHECK_TABLE S;  int i;
69   ifdebug(DMA, D, DebugRegisterUsage(MEM_DBCHECK, 1,
70     2*sizeof(int) + newsize * sizeof(OBJECT)));
71   S = (DBCHECK_TABLE)
72 	  malloc(2*sizeof(int) + newsize * sizeof(OBJECT));
73   if( S == (DBCHECK_TABLE) NULL )
74     Error(33, 1, "run out of memory enlarging dbcheck table", FATAL, no_fpos);
75   dtab_size(S) = newsize;
76   dtab_count(S) = 0;
77   for( i = 0;  i < newsize;  i++ )  dtab_item(S, i) = nilobj;
78   return S;
79 } /* end dtab_new */
80 
81 static void dtab_insert(OBJECT x, DBCHECK_TABLE *S);
82 
dtab_rehash(DBCHECK_TABLE S,int newsize)83 static DBCHECK_TABLE dtab_rehash(DBCHECK_TABLE S, int newsize)
84 { DBCHECK_TABLE NewS;  int i;  OBJECT link, z;
85   NewS = dtab_new(newsize);
86   for( i = 0;  i < dtab_size(S);  i++ )
87   { if( dtab_item(S, i) != nilobj )
88     { OBJECT ent = dtab_item(S, i);
89       assert( type(ent) == ACAT, "dtab_rehash: ACAT!" );
90       for( link = Down(ent);  link != ent;  link = NextDown(link) )
91       { Child(z, link);
92 	dtab_insert(z, &NewS);
93       }
94       DisposeObject(ent);
95     }
96   }
97   ifdebug(DMA, D, DebugRegisterUsage(MEM_DBCHECK, -1,
98     -(2*sizeof(int) + dtab_size(S) * sizeof(OBJECT))));
99   free(S);
100   return NewS;
101 } /* end dtab_rehash */
102 
dtab_insert(OBJECT x,DBCHECK_TABLE * S)103 static void dtab_insert(OBJECT x, DBCHECK_TABLE *S)
104 { unsigned long pos;  OBJECT z, link, y;
105   if( dtab_count(*S) == dtab_size(*S) - 1 )	/* one less since 0 unused */
106     *S = dtab_rehash(*S, 2*dtab_size(*S));
107   dtab_count(*S)++;
108   hash(pos, db_checksym(x), string(x), *S);
109   if( dtab_item(*S, pos) == nilobj )  New(dtab_item(*S, pos), ACAT);
110   z = dtab_item(*S, pos);
111   for( link = Down(z);  link != z;  link = NextDown(link) )
112   { Child(y, link);
113     if( db_checksym(x) == db_checksym(y) && StringEqual(string(x), string(y)) )
114     { assert(FALSE, "Dbcheck: entry inserted twice");
115     }
116   }
117   Link(dtab_item(*S, pos), x);
118 } /* end dtab_insert */
119 
dtab_retrieve(OBJECT sym,FULL_CHAR * tag,DBCHECK_TABLE S)120 static OBJECT dtab_retrieve(OBJECT sym, FULL_CHAR *tag, DBCHECK_TABLE S)
121 { OBJECT x, link, y;  unsigned long pos;
122   hash(pos, sym, tag, S);
123   x = dtab_item(S, pos);
124   if( x == nilobj )  return nilobj;
125   for( link = Down(x);  link != x;  link = NextDown(link) )
126   { Child(y, link);
127     if( sym == db_checksym(y) && StringEqual(tag, string(y)) )
128       return y;
129   }
130   return nilobj;
131 } /* end dtab_retrieve */
132 
133 #if DEBUG_ON
dtab_debug(DBCHECK_TABLE S,FILE * fp)134 static void dtab_debug(DBCHECK_TABLE S, FILE *fp)
135 { int i;  OBJECT x, link, y;
136   fprintf(fp, "  table size: %d;  current number of items: %d%s",
137     dtab_size(S), dtab_count(S), STR_NEWLINE);
138   for( i = 0;  i < dtab_size(S);  i++ )
139   { x = dtab_item(S, i);
140     fprintf(fp, "dtab_item(S, %d) =", i);
141     if( x == nilobj )
142       fprintf(fp, " <nilobj>");
143     else if( type(x) != ACAT )
144       fprintf(fp, " not ACAT!");
145     else for( link = Down(x);  link != x;  link = NextDown(link) )
146     { Child(y, link);
147       fprintf(fp, " %s&&%s",
148 	is_word(type(y)) ? SymName(db_checksym(y)) : AsciiToFull("?"),
149 	is_word(type(y)) ? string(y) : AsciiToFull("not-WORD!"));
150     }
151     fprintf(fp, "%s", STR_NEWLINE);
152   }
153 } /* end dtab_debug */
154 #endif
155 
156 static DBCHECK_TABLE DbCheckTable;		/* the dbcheck table         */
157 static BOOLEAN	     DbCheckTableInit;		/* TRUE if table inited	     */
158 static int extra_seq;
159 
160 
161 /*****************************************************************************/
162 /*                                                                           */
163 /*  OldCrossDb     Database containing cross references from previous run.   */
164 /*  NewCrossDb     Writable database of cross references from this run.      */
165 /*                                                                           */
166 /*****************************************************************************/
167 
168 OBJECT OldCrossDb, NewCrossDb;
169 
170 
171 /*****************************************************************************/
172 /*                                                                           */
173 /*  void DbInit(void)                                                        */
174 /*                                                                           */
175 /*  Initialize this module..                                                 */
176 /*                                                                           */
177 /*****************************************************************************/
178 
DbInit(void)179 void DbInit(void)
180 {
181   DbCheckTable = NULL;
182   DbCheckTableInit = FALSE;
183   OldCrossDb = NewCrossDb = nilobj;
184   extra_seq = 0;
185 }
186 
187 
188 /*****************************************************************************/
189 /*                                                                           */
190 /*  #define SymToNum(db, sym, num, gall)                                     */
191 /*                                                                           */
192 /*  Set num to the number used to refer to sym in database db.  If sym is    */
193 /*  not currently referred to in db, create a new number and record sym.     */
194 /*  If gall is true, sym is the target of galleys stored in this database.   */
195 /*  Store in boolean fields db_targ(link) and is_extern_target(sym).         */
196 /*                                                                           */
197 /*****************************************************************************/
198 
199 #define SymToNum(db, sym, num, gall)					\
200 { OBJECT link, yy;  int count;						\
201   count = 0;								\
202   for( link = Down(db);  link != db;  link = NextDown(link) )		\
203   { Child(yy, link);							\
204     assert(type(yy)==CROSS_SYM || type(yy)==ACAT, "SymToNum: yy!");	\
205     if( type(yy) != CROSS_SYM )  continue;				\
206     if( symb(yy) == sym )  break;					\
207     if( number(link) > count )  count = number(link);			\
208   }									\
209   if( link == db )							\
210   { if( cross_sym(sym) == nilobj )  CrossInit(sym);			\
211     Link(db, cross_sym(sym));						\
212     link = LastDown(db);						\
213     number(link) = count + 1;						\
214     db_targ(link) = FALSE;						\
215   }									\
216   num = number(link);							\
217   if( gall )  db_targ(link) = is_extern_target(sym) =			\
218 				uses_extern_target(sym) = TRUE;		\
219 } /* end SymToNum */
220 
221 
222 /*@::NumToSym(), DbCreate()@**************************************************/
223 /*                                                                           */
224 /*  #define NumToSym(db, num, sym)                                           */
225 /*                                                                           */
226 /*  Set sym to the symbol which is referred to in database db by num.        */
227 /*                                                                           */
228 /*****************************************************************************/
229 
230 #define NumToSym(db, num, sym)						\
231 { OBJECT link, y = nilobj;						\
232   for( link = Down(db);  link != db;  link = NextDown(link) )		\
233   { Child(y, link);							\
234     if( type(y) == CROSS_SYM && number(link) == num )  break;		\
235   }									\
236   assert( link != db, "NumToSym: no sym");				\
237   assert( type(y) == CROSS_SYM, "NumToSym: y!" );			\
238   sym = symb(y);							\
239 } /* end NumToSym */
240 
241 
242 /*****************************************************************************/
243 /*                                                                           */
244 /*  OBJECT DbCreate(x)                                                       */
245 /*                                                                           */
246 /*  Create a new writable database with name (i.e. file stem) x and file     */
247 /*  position fpos for error messages.                                        */
248 /*                                                                           */
249 /*****************************************************************************/
250 
DbCreate(OBJECT x)251 OBJECT DbCreate(OBJECT x)
252 { OBJECT db = x;
253   debug1(DBS, DD, "DbCreate(%s)", string(db));
254   assert( is_word(type(x)), "DbCreate: !is_word(type(x))" );
255   reading(db) = FALSE;  db_filep(db) = null;
256   debug1(DBS, DD, "DbCreate returning %s", EchoObject(db));
257   return db;
258 } /* end DbCreate */
259 
260 
261 /*@::DbInsert()@**************************************************************/
262 /*                                                                           */
263 /*  DbInsert(db, gall, sym, tag, tagfpos, seq, dfnum, dlnum, dfpos)          */
264 /*                                                                           */
265 /*  Insert a new entry into writable database db.  The primary key of the    */
266 /*  entry has these three parts:                                             */
267 /*                                                                           */
268 /*      gall        TRUE if inserting a galley                               */
269 /*      sym         The symbol which is the target of this entry             */
270 /*      tag         The tag of this target (must be a non-null string)       */
271 /*                                                                           */
272 /*  tagfpos is the file position that the tag originated from.               */
273 /*  There is also an auxiliary key, seq, which enforces an ordering on       */
274 /*  entries with equal primary keys but is not itself ever retrieved.  This  */
275 /*  ordering is used for sorted galleys.  The value of the entry has the     */
276 /*  following parts:                                                         */
277 /*                                                                           */
278 /*      dfnum       The file containing the object                           */
279 /*      dfpos       The position of the object in that file                  */
280 /*      dlnum       The line number of the object in the file                */
281 /*                                                                           */
282 /*  If check is TRUE, we need to check whether an entry with this key has    */
283 /*  been inserted before.  This will never be the case with galley entries.  */
284 /*                                                                           */
285 /*****************************************************************************/
286 
DbInsert(OBJECT db,BOOLEAN gall,OBJECT sym,FULL_CHAR * tag,FILE_POS * tagfpos,FULL_CHAR * seq,FILE_NUM dfnum,long dfpos,int dlnum,BOOLEAN check)287 void DbInsert(OBJECT db, BOOLEAN gall, OBJECT sym, FULL_CHAR *tag,
288 FILE_POS *tagfpos, FULL_CHAR *seq, FILE_NUM dfnum, long dfpos, int dlnum,
289 BOOLEAN check)
290 { int symnum;  OBJECT chk;
291   FULL_CHAR buff[MAX_BUFF];
292   assert( is_word(type(db)), "DbInsert: db!" );
293   assert( tag[0] != '\0', "DbInsert: null tag!" );
294   assert( seq[0] != '\0', "DbInsert: null seq!" );
295   ifdebug(DPP, D, ProfileOn("DbInsert"));
296   debug6(DBS, DD, "DbInsert(%s, %s, %s, %s, %s, %s, dlnum, dfpos)",
297 	string(db), bool(gall), SymName(sym), tag, seq,
298 	dfnum == NO_FILE ? AsciiToFull(".") : FileName(dfnum));
299   assert(!reading(db), "DbInsert: insert into reading database");
300 
301   /* if required, check that (sym, tag) not already inserted */
302   if( check )
303   {
304     debug2(DBS, DD, "  checking %s&&%s, DbCheckTable =", SymName(sym), tag);
305     if( !DbCheckTableInit )
306     { DbCheckTable = dtab_new(INIT_DBCHECK_NUM);
307       DbCheckTableInit = TRUE;
308     }
309     ifdebug(DBS, DD, dtab_debug(DbCheckTable, stderr));
310     chk = dtab_retrieve(sym, tag, DbCheckTable);
311     if( chk == nilobj )
312     { chk = MakeWord(WORD, tag, tagfpos);
313       db_checksym(chk) = sym;
314       dtab_insert(chk, &DbCheckTable);
315     }
316     else
317     { if( file_num(fpos(chk)) > 0 )
318         Error(33, 4, "cross reference %s&&%s used previously, at%s",
319           WARN, tagfpos, SymName(sym), tag, EchoFilePos(&fpos(chk)));
320       else Error(33, 5, "cross reference %s&&%s used previously",
321 	  WARN, tagfpos, SymName(sym), tag);
322     }
323   }
324 
325   /* open database index file if not already done */
326   if( db_filep(db) == null )
327   { if( StringLength(string(db)) + StringLength(NEW_INDEX_SUFFIX) >= MAX_BUFF )
328       Error(33, 2, "database file name %s%s is too long",
329 	FATAL, no_fpos, string(db), NEW_INDEX_SUFFIX);
330     StringCopy(buff, string(db));
331     StringCat(buff, NEW_INDEX_SUFFIX);
332     db_filep(db) = StringFOpen(buff, WRITE_FILE);
333     if( db_filep(db) == null )
334       Error(33, 3, "cannot write to database file %s", FATAL, &fpos(db), buff);
335   }
336 
337   /* work out database index file entry and append it to file */
338   if( dfnum != NO_FILE )
339   { StringCopy(buff, FileName(dfnum));
340     StringCopy(&buff[StringLength(buff)-StringLength(DATA_SUFFIX)], STR_EMPTY);
341   }
342   else StringCopy(buff, AsciiToFull("."));
343   SymToNum(db, sym, symnum, gall);
344   ifdebug(DBS, DD,
345     fprintf(stderr, "  -> %s%d&%s\t%s\t%ld\t%d\t%s%s", gall ? "0" : "", symnum,
346       tag, seq, dfpos, dlnum, buff, STR_NEWLINE);
347   );
348   fprintf(db_filep(db), "%s%d&%s\t%s\t%s\t%ld\t%d\t%s%s", gall ? "0" : "",
349     symnum, tag, seq, StringFiveInt(++extra_seq), dfpos, dlnum, buff,
350     STR_NEWLINE);
351 
352   /* return */
353   debug0(DBS, DD, "DbInsert returning.");
354   ifdebug(DPP, D, ProfileOff("DbInsert"));
355 } /* end DbInsert */
356 
357 
358 /*@::DbConvert(), DbClose()@**************************************************/
359 /*                                                                           */
360 /*  DbConvert(db, full_name)                                                 */
361 /*                                                                           */
362 /*  Convert database db from writable to readable, then dispose it.          */
363 /*  full_name is TRUE if symbols are to be known by their full path name.    */
364 /*                                                                           */
365 /*****************************************************************************/
366 
DbConvert(OBJECT db,BOOLEAN full_name)367 void DbConvert(OBJECT db, BOOLEAN full_name)
368 { FULL_CHAR oldname[MAX_BUFF+10], newname[MAX_BUFF];
369   OBJECT link, y;
370   ifdebug(DPP, D, ProfileOn("DbConvert"));
371   debug2(DBS, DD, "DbConvert( %ld %s )", (long) db, string(db));
372   assert( !reading(db), "DbConvert: reading database");
373   StringCopy(newname, string(db));
374   StringCat(newname, INDEX_SUFFIX);
375   StringCopy(oldname, string(db));
376   StringCat(oldname, NEW_INDEX_SUFFIX);
377   if( db_filep(db) != null )
378   {
379     fprintf(db_filep(db), "00 %s %s%s", LOUT_VERSION, "database index file",
380       (char *) STR_NEWLINE);
381     for( link = Down(db);  link != db;  link = NextDown(link) )
382     { Child(y, link);
383       assert( type(y) == CROSS_SYM || type(y) == ACAT, "DbConvert: y!" );
384       if( type(y) != CROSS_SYM )  continue;
385       fprintf(db_filep(db), "%s %d %s%s",
386 	db_targ(link) ? "00target" : "00symbol", number(link),
387 	full_name ? FullSymName(symb(y), AsciiToFull(" ")) : SymName(symb(y)),
388 	(char *) STR_NEWLINE);
389     }
390     fclose(db_filep(db));
391     debug2(DBS, DD, "  calling SortFile(%s, %s)", oldname, newname);
392     SortFile(oldname, newname);
393   }
394   else StringRemove(newname);
395   StringRemove(oldname);
396   DeleteNode(db);
397   debug0(DBS, DD, "DbConvert returning.");
398   ifdebug(DPP, D, ProfileOff("DbConvert"));
399 } /* end DbConvert */
400 
401 
402 /*****************************************************************************/
403 /*                                                                           */
404 /*  DbClose(db)                                                              */
405 /*                                                                           */
406 /*  Close readable database db.                                              */
407 /*                                                                           */
408 /*****************************************************************************/
409 
DbClose(OBJECT db)410 void DbClose(OBJECT db)
411 { if( db != nilobj && !in_memory(db) && db_filep(db) != NULL )
412   {  fclose(db_filep(db));
413      db_filep(db) = NULL;
414   }
415 } /* end DbClose */
416 
417 
418 /*@::DbLoad()@****************************************************************/
419 /*                                                                           */
420 /*  OBJECT DbLoad(stem, fpath, create, symbs, in_mem)                        */
421 /*                                                                           */
422 /*  Open for reading the database whose index file name is string(stem).li.  */
423 /*  This file has not yet been defined; its search path is fpath.  If it     */
424 /*  will not open and create is true, try creating it from string(stem).ld.  */
425 /*                                                                           */
426 /*  symbs is an ACAT of CLOSUREs showing the symbols that the database may   */
427 /*  contain; or nilobj if the database may contain any symbol.               */
428 /*                                                                           */
429 /*  If in_mem is true, this database index is to be kept in internal memory, */
430 /*  rather than an external file, as a speed optimization.                   */
431 /*                                                                           */
432 /*****************************************************************************/
433 
DbLoad(OBJECT stem,int fpath,BOOLEAN create,OBJECT symbs,BOOLEAN in_mem)434 OBJECT DbLoad(OBJECT stem, int fpath, BOOLEAN create, OBJECT symbs,
435   BOOLEAN in_mem)
436 { FILE *fp;  OBJECT db, t, res, tag, par, sym, link, y;
437   int i, lnum, dlnum, num, count, leftp;
438   FILE_NUM index_fnum, dfnum;  long dfpos;
439   BOOLEAN gall;  FULL_CHAR line[MAX_BUFF], sym_name[MAX_BUFF]; int status;
440   ifdebug(DPP, D, ProfileOn("DbLoad"));
441   debug3(DBS, DD, "[ DbLoad(%s, %d, %s, -)", string(stem), fpath, bool(create));
442 
443   /* open or else create index file fp */
444   debug0(DFS, D, "  calling DefineFile from DbLoad (1)");
445   index_fnum = DefineFile(string(stem), INDEX_SUFFIX, &fpos(stem), INDEX_FILE,
446 		 fpath);
447   fp = OpenFile(index_fnum, create, FALSE);
448 
449   /* read first line of database index file, which should have the version */
450   if( fp != null )
451   { if( ReadOneLine(fp, line, MAX_BUFF) == 0 ||
452         !StringBeginsWith(&line[3], LOUT_VERSION) )
453     {
454       /* out of date, pretend it isn't there at all */
455       StringRemove(FileName(index_fnum));
456       fp = null;
457     }
458   }
459 
460   if( fp == null && create )
461   { db = nilobj;
462     debug0(DFS, D, "  calling DefineFile from DbLoad (2)");
463     dfnum = DefineFile(string(stem), DATA_SUFFIX, &fpos(stem),
464       DATABASE_FILE, fpath);
465     dfpos = 0L;  LexPush(dfnum, 0, DATABASE_FILE, 1, FALSE);
466     t = LexGetToken();
467     dlnum = line_num(fpos(t));
468     while( type(t) == LBR )
469     { res = Parse(&t, StartSym, FALSE, FALSE);
470       if( t != nilobj || type(res) != CLOSURE )
471 	Error(33, 6, "syntax error in database file %s",
472 	  FATAL, &fpos(res), FileName(dfnum));
473       assert( symbs != nilobj, "DbLoad: create && symbs == nilobj!" );
474       if( symbs != nilobj )
475       {	for( link = Down(symbs);  link != symbs;  link = NextDown(link) )
476 	{ Child(y, link);
477 	  if( type(y) == CLOSURE && actual(y) == actual(res) )  break;
478 	}
479 	if( link == symbs )
480 	  Error(33, 7, "%s found in database but not declared in %s line",
481 	    FATAL, &fpos(res), SymName(actual(res)), KW_DATABASE);
482       }
483       for( tag = nilobj, link = Down(res); link != res; link = NextDown(link) )
484       {	Child(par, link);
485 	if( type(par) == PAR && is_tag(actual(par)) && Down(par) != par )
486 	{ Child(tag, Down(par));
487 	  break;
488 	}
489       }
490       if( tag == nilobj )
491 	Error(33, 8, "database symbol %s has no tag",
492 	  FATAL, &fpos(res), SymName(actual(res)));
493       tag = ReplaceWithTidy(tag, WORD_TIDY);  /* && */
494       if( !is_word(type(tag)) )
495 	Error(33, 9, "database symbol tag is not a simple word",
496 	  FATAL, &fpos(res));
497       if( StringEqual(string(tag), STR_EMPTY) )
498 	Error(33, 10, "database symbol tag is an empty word", FATAL,&fpos(res));
499       if( db == nilobj )
500       {	StringCopy(line, FileName(dfnum));
501 	i = StringLength(line) - StringLength(INDEX_SUFFIX);
502 	assert( i > 0, "DbLoad: FileName(dfnum) (1)!" );
503 	StringCopy(&line[i], STR_EMPTY);
504 	db = DbCreate(MakeWord(WORD, line, &fpos(stem)));
505       }
506       DbInsert(db, FALSE, actual(res), string(tag), &fpos(tag), STR_ZERO,
507 	NO_FILE, dfpos, dlnum, TRUE);
508       DisposeObject(res);  dfpos = LexNextTokenPos();  t = LexGetToken();
509       dlnum = line_num(fpos(t));
510     }
511     if( type(t) != END )
512       Error(33, 11, "%s or end of file expected here", FATAL, &fpos(t), KW_LBR);
513     LexPop();
514     if( db == nilobj )
515     { StringCopy(line, FileName(dfnum));
516       i = StringLength(line) - StringLength(INDEX_SUFFIX);
517       assert( i > 0, "DbLoad: FileName(dfnum) (2)!" );
518       StringCopy(&line[i], STR_EMPTY);
519       db = DbCreate(MakeWord(WORD, line, &fpos(stem)));
520     }
521     DbConvert(db, FALSE);
522     if( (fp = OpenFile(index_fnum, FALSE, FALSE)) == null ||
523         ReadOneLine(fp, line, MAX_BUFF) == 0 ||
524         !StringBeginsWith(&line[3], LOUT_VERSION) )
525       Error(33, 12, "cannot open database file %s",
526         FATAL, &fpos(db), FileName(index_fnum));
527   }
528 
529   /* set up database record */
530   StringCopy(line, FileName(index_fnum));
531   i = StringLength(line) - StringLength(INDEX_SUFFIX);
532   assert( i > 0, "DbLoad: FileName(index_fnum)!" );
533   StringCopy(&line[i], STR_EMPTY);
534   db = MakeWord(WORD, line, &fpos(stem));
535   reading(db) = TRUE;
536   in_memory(db) = in_mem;
537   if( symbs != nilobj )
538   { assert( type(symbs) == ACAT, "DbLoad: type(symbs)!" );
539     Link(db, symbs);
540   }
541   if( fp == null )
542   { debug1(DBS, DD, "] DbLoad returning (empty) %s", string(db));
543     db_filep(db) = null;
544     db_lines(db) = (LINE *) NULL;
545     ifdebug(DPP, D, ProfileOff("DbLoad"));
546     return db;
547   }
548 
549   /* read header lines of index file, find its symbols */
550   leftp = 0;  lnum = 1;
551   while( (status = ReadOneLine(fp, line, MAX_BUFF)) != 0 )
552   {
553     debug1(DBS, D, "ReadOneLine returning \"%s\"", line);
554     if( line[0] != '0' || line[1] != '0' )  break;
555     lnum++;
556     leftp = (int) ftell(fp);
557     gall = StringBeginsWith(line, AsciiToFull("00target "));
558     sscanf( (char *) line, gall ? "00target %d" : "00symbol %d", &num);
559     for( i = 9;  line[i] != CH_SPACE && line[i] != '\0';  i++ );
560     if( symbs == nilobj )
561     {
562       /* any symbols are possible, full path names in index file required */
563       count = 0;  sym = StartSym;
564       while( line[i] != '\0' )
565       {	PushScope(sym, FALSE, FALSE);  count++;
566 	sscanf( (char *) &line[i+1], "%s", sym_name);
567 	sym = SearchSym(sym_name, StringLength(sym_name));
568 	i += StringLength(sym_name) + 1;
569       }
570       for( i = 1;  i <= count;  i++ )  PopScope();
571     }
572     else
573     {
574       /* only symbs symbols possible, full path names not required */
575       sym = nilobj;
576       sscanf( (char *) &line[i+1], "%s", sym_name);
577       for( link = Down(symbs);  link != symbs;  link = NextDown(link) )
578       {	Child(y, link);
579 	assert( type(y) == CLOSURE, "DbLoad: type(y) != CLOSURE!" );
580 	if( StringEqual(sym_name, SymName(actual(y))) )
581 	{ sym = actual(y);
582 	  break;
583 	}
584       }
585     }
586     if( sym != nilobj && sym != StartSym )
587     { if( cross_sym(sym) == nilobj )  CrossInit(sym);
588       Link(db, cross_sym(sym));
589       link = LastDown(db);
590       number(link) = num;  db_targ(link) = gall;
591       if( gall )  is_extern_target(sym) = uses_extern_target(sym) = TRUE;
592     }
593     else
594     { Error(33, 13, "undefined symbol in database file %s (line %d)",
595 	WARN, &fpos(db), FileName(index_fnum), lnum);
596       debug1(DBS, DD, "] DbLoad returning %s (error)", string(db));
597       fclose(fp);
598       in_memory(db) = FALSE;
599       db_filep(db) = null;  /* subsequently treated like an empty database */
600       ifdebug(DPP, D, ProfileOff("DbLoad"));
601       return db;
602     }
603   }
604 
605   /* if in_memory, go on to read the entire database index into memory */
606   if( in_memory(db) )
607   {
608     if( status == 0 )
609       db_lines(db) = 0;
610     else
611     {
612       int len;
613       db_lines(db) = ReadLines(fp, FileName(index_fnum), line, &len);
614       db_lineslen(db) = len;
615       SortLines(db_lines(db), db_lineslen(db));
616     }
617   }
618   else /* external, save leftpos and file pointer */
619   { db_filep(db) = fp;
620     left_pos(db) = leftp;
621   }
622 
623   /* return */
624   debug1(DBS, DD, "] DbLoad returning %s", string(db));
625   ifdebug(DPP, D, ProfileOff("DbLoad"));
626   return db;
627 } /* end DbLoad */
628 
629 
630 /*@::SearchFile()@************************************************************/
631 /*                                                                           */
632 /*  static BOOLEAN SearchFile(fp, left, right, str, line)                    */
633 /*                                                                           */
634 /*  File fp is a text file.  left is the beginning of a line, right is the   */
635 /*  end of a line.   Search the file by binary search for a line beginning   */
636 /*  with str.  If found, return it in line, else return FALSE.               */
637 /*                                                                           */
638 /*  NB if the line endings consist of two characters, the end of the line    */
639 /*  is at the second character.                                              */
640 /*                                                                           */
641 /*****************************************************************************/
642 
SearchFile(FILE * fp,int left,int right,FULL_CHAR * str,FULL_CHAR * line)643 static BOOLEAN SearchFile(FILE *fp, int left, int right,
644 FULL_CHAR *str, FULL_CHAR *line)
645 { int l, r, mid, mid_end;  FULL_CHAR buff[MAX_BUFF];  BOOLEAN res;
646   int ch;
647   ifdebug(DPP, D, ProfileOn("SearchFile"));
648   debug3(DBS, D, "SearchFile(fp, %d, %d, %s, line)", left, right, str);
649 
650   l = left;  r = right;
651   while( l <= r )
652   {
653     /* loop invt: (l==0 or fp[l-1]==end_of_line) and (fp[r] == end_of_line)  */
654     /* and first key >= str lies in the range fp[l..r+1]                     */
655 
656     /* find line near middle of the range; mid..mid_end brackets it */
657     debug2(DBS, DD, "  start loop: l = %d, r = %d", l, r);
658     mid = (l + r)/2;
659     fseek(fp, (long) mid, SEEK_SET);
660     do { mid++; } while( (ch = getc(fp)) != CH_CR && ch != CH_LF );
661     if( ch == CH_CR )
662     {
663       ch = getc(fp);
664       if( ch != CH_LF )
665 	ungetc(ch, fp);
666       else
667 	mid++;
668     }
669     else /* ch == CH_LF */
670     {
671       ch = getc(fp);
672       if( ch != CH_CR )
673 	ungetc(ch, fp);
674       else
675 	mid++;
676     }
677     if( mid == r + 1 )
678     { mid = l;
679       fseek(fp, (long) mid, SEEK_SET);
680     }
681     ReadOneLine(fp, line, MAX_BUFF);
682     mid_end = (int) ftell(fp) - 1;
683     debug3(DBS, DD, "  mid: %d, mid_end: %d, line: %s", mid, mid_end, line);
684     assert( l <= mid,      "SearchFile: l > mid!"        );
685     assert( mid < mid_end, "SearchFile: mid >= mid_end!" );
686     assert( mid_end <= r,  "SearchFile: mid_end > r!"    );
687 
688     /* compare str with this line and prepare next step */
689     if( TabbedStringLessEqual(str, line) )
690     {
691       debug2(DBS, D, "  left after comparing key %s with line %s", str, line);
692       r = mid - 1;
693     }
694     else
695     {
696       debug2(DBS, D, "  right after comparing key %s with line %s", str, line);
697       l = mid_end + 1;
698     }
699   } /* end while */
700 
701   /* now first key >= str lies in fp[l]; compare it with str */
702   if( l < right )
703   { fseek(fp, (long) l, SEEK_SET);
704     ReadOneLine(fp, line, MAX_BUFF);
705     sscanf( (char *) line, "%[^\t]", buff);
706     res = StringEqual(str, buff);
707   }
708   else res = FALSE;
709   debug1(DBS, D, "SearchFile returning %s", bool(res));
710   ifdebug(DPP, D, ProfileOff("SearchFile"));
711   return res;
712 } /* end SearchFile */
713 
714 
715 /*@::SearchLines()@***********************************************************/
716 /*                                                                           */
717 /*  static BOOLEAN SearchLines(LINE *lines, int left, int right, str, lnum)  */
718 /*                                                                           */
719 /*  Search the sorted array of LINE arrays lines[left..right] for a line     */
720 /*  beginning with str, and return TRUE if found else FALSE.                 */
721 /*                                                                           */
722 /*  If TRUE is returned then the number of the line is in *lnum.             */
723 /*                                                                           */
724 /*****************************************************************************/
725 
SearchLines(LINE * lines,int left,int right,FULL_CHAR * str,int * lnum)726 static BOOLEAN SearchLines(LINE *lines, int left, int right, FULL_CHAR *str,
727   int *lnum)
728 { int l, r, mid;  FULL_CHAR buff[MAX_BUFF];
729   BOOLEAN res;
730   debug3(DBS, D, "SearchLines(lines, %d, %d, %s, lnum)", left, right, str);
731   if( right < left )
732   {
733     debug0(DBS, D, "SearchLines returning FALSE (empty lines)");
734     return FALSE;
735   }
736   l = left;
737   r = right - 1;
738   while( l <= r )
739   {
740     /* loop invt: first key >= str (if any) lies in the range lines[l..r+1] */
741     /* and left <= l <= right and r < right                                 */
742     mid = (l + r) / 2;
743     debug4(DBS, D, "  [l %d, r %d] examining lines[%d] = %s", l, r, mid,
744       lines[mid]);
745     if( TabbedStringLessEqual(str, (FULL_CHAR *) lines[mid]) )  r = mid - 1;
746     else l = mid + 1;
747   }
748   sscanf( (char *) lines[l], "%[^\t]", buff);
749   if( StringEqual(str, buff) )
750   {
751     res = TRUE;
752     *lnum = l;
753     debug1(DBS, D, "SearchLines returning TRUE (lnum %d)", *lnum);
754   }
755   else
756   { res = FALSE;
757     debug0(DBS, D, "SearchLines returning FALSE");
758   }
759   return res;
760 } /* end SearchLines */
761 
762 
763 /*@::DbRetrieve()@************************************************************/
764 /*                                                                           */
765 /*  BOOLEAN DbRetrieve(db, gall, sym, tag, seq, dfnum, dfpos, dlnum, cont)   */
766 /*                                                                           */
767 /*  Retrieve the first entry of database db with the given gall, sym and     */
768 /*  tag.  Set *seq, *dfnum, *dlnum, *dfpos to the associated value.          */
769 /*  Set *cont to a private value for passing to DbRetrieveNext.              */
770 /*                                                                           */
771 /*****************************************************************************/
772 
DbRetrieve(OBJECT db,BOOLEAN gall,OBJECT sym,FULL_CHAR * tag,FULL_CHAR * seq,FILE_NUM * dfnum,long * dfpos,int * dlnum,long * cont)773 BOOLEAN DbRetrieve(OBJECT db, BOOLEAN gall, OBJECT sym, FULL_CHAR *tag,
774   FULL_CHAR *seq, FILE_NUM *dfnum, long *dfpos, int *dlnum, long *cont)
775 { int symnum, lnum;  FULL_CHAR line[MAX_BUFF], buff[MAX_BUFF];
776   ifdebug(DPP, D, ProfileOn("DbRetrieve"));
777   debug4(DBS, DD, "DbRetrieve(%s, %s%s&%s)", string(db), gall ? "0" : "",
778 	SymName(sym), tag);
779 
780   /* check OK to proceed */
781   if( !reading(db) || db_filep(db) == null )
782   { debug0(DBS, DD, "DbRetrieve returning FALSE (empty or not reading)");
783     ifdebug(DPP, D, ProfileOff("DbRetrieve"));
784     return FALSE;
785   }
786 
787   /* convert parameters into search key */
788   SymToNum(db, sym, symnum, FALSE);
789   sprintf( (char *) buff, "%s%d&%s", gall ? "0" : "", symnum, tag);
790 
791   if( in_memory(db) )
792   {
793     /* search internal table, return if not found; set *cont to continuation */
794     if( !SearchLines(db_lines(db), 0, db_lineslen(db) - 1, buff, &lnum) )
795     { debug0(DBS, DD, "DbRetrieve returning FALSE (key not present)");
796       ifdebug(DPP, D, ProfileOff("DbRetrieve"));
797       return FALSE;
798     }
799     sscanf( (char *) db_lines(db)[lnum],
800       "%*[^\t]\t%[^\t]\t%*[^\t]\t%ld\t%d\t%[^\n\f]", seq, dfpos, dlnum, buff);
801     *cont = lnum+1;
802   }
803   else
804   {
805     /* search for key in file, return if not found; set *cont to continuatn */
806     fseek(db_filep(db), 0L, SEEK_END);
807     if( !SearchFile(db_filep(db), (int) left_pos(db),
808 	   (int) ftell(db_filep(db)) - 1, buff, line) )
809     { debug0(DBS, DD, "DbRetrieve returning FALSE (key not present)");
810       ifdebug(DPP, D, ProfileOff("DbRetrieve"));
811       return FALSE;
812     }
813     sscanf( (char *) line,
814       "%*[^\t]\t%[^\t]\t%*[^\t]\t%ld\t%d\t%[^\n\f]", seq, dfpos, dlnum, buff);
815     *cont = ftell(db_filep(db));
816   }
817 
818   /* work out file name if . abbreviation used, and possibly define file */
819   if( StringEqual(buff, AsciiToFull(".")) )
820   { StringCopy(buff, string(db));
821   }
822   *dfnum = FileNum(buff, DATA_SUFFIX);
823   if( *dfnum == NO_FILE )  /* can only occur in cross reference database */
824   { debug0(DFS, D, "  calling DefineFile from DbRetrieve");
825     *dfnum = DefineFile(buff, DATA_SUFFIX, &fpos(db),
826       DATABASE_FILE, SOURCE_PATH);
827   }
828 
829   /* return */
830   debug3(DBS, DD, "DbRetrieve returning TRUE (in %s at %ld, line %d)",
831     FileName(*dfnum), *dfpos, *dlnum);
832   ifdebug(DPP, D, ProfileOff("DbRetrieve"));
833   return TRUE;
834 } /* end DbRetrieve */
835 
836 
837 /*@::DbRetrieveNext()@********************************************************/
838 /*                                                                           */
839 /*  BOOLEAN DbRetrieveNext(db, gall, sym, tag, seq, dfnum, dfpos,dlnum,cont) */
840 /*                                                                           */
841 /*  Retrieve the entry of database db pointed to by *cont.                   */
842 /*  Set *gall, *sym, *tag, *seq, *dfnum, *dlnum, *dfpos to the value.        */
843 /*  Reset *cont to the next entry for passing to the next DbRetrieveNext.    */
844 /*                                                                           */
845 /*****************************************************************************/
846 
DbRetrieveNext(OBJECT db,BOOLEAN * gall,OBJECT * sym,FULL_CHAR * tag,FULL_CHAR * seq,FILE_NUM * dfnum,long * dfpos,int * dlnum,long * cont)847 BOOLEAN DbRetrieveNext(OBJECT db, BOOLEAN *gall, OBJECT *sym, FULL_CHAR *tag,
848   FULL_CHAR *seq, FILE_NUM *dfnum, long *dfpos, int *dlnum, long *cont)
849 { FULL_CHAR line[MAX_BUFF], *cline, fname[MAX_BUFF]; int symnum;
850   ifdebug(DPP, D, ProfileOn("DbRetrieveNext"));
851   debug2(DBS, DD, "DbRetrieveNext( %s, %ld )", string(db), *cont);
852   assert(reading(db), "DbRetrieveNext: not reading");
853 
854   /* check OK to proceed */
855   if( db_filep(db) == null )
856   { debug0(DBS, DD, "DbRetrieveNext returning FALSE (empty database)");
857     ifdebug(DPP, D, ProfileOff("DbRetrieveNext"));
858     return FALSE;
859   }
860 
861   if( in_memory(db) )
862   {
863     /* get next entry from internal database */
864     if( *cont >= db_lineslen(db) )
865     { debug0(DBS, DD, "DbRetrieveNext returning FALSE (no successor)");
866       ifdebug(DPP, D, ProfileOff("DbRetrieveNext"));
867       return FALSE;
868     }
869     cline = (FULL_CHAR *) db_lines(db)[*cont];
870     *gall = (cline[0] == '0' ? 1 : 0);
871     sscanf((char *)&cline[*gall], "%d&%[^\t]\t%[^\t]\t%*[^\t]\t%ld\t%d\t%[^\n\f]",
872       &symnum, tag, seq, dfpos, dlnum, fname);
873     *cont = *cont + 1;
874   }
875   else
876   {
877     /* use *cont to find position of next entry; advance *cont */
878     fseek(db_filep(db), *cont == 0L ? (long) left_pos(db) : *cont, SEEK_SET);
879     if( ReadOneLine(db_filep(db), line, MAX_BUFF) == 0 )
880     { debug0(DBS, DD, "DbRetrieveNext returning FALSE (no successor)");
881       ifdebug(DPP, D, ProfileOff("DbRetrieveNext"));
882       return FALSE;
883     }
884     *gall = (line[0] == '0' ? 1 : 0);
885     sscanf((char *)&line[*gall], "%d&%[^\t]\t%[^\t]\t%*[^\t]\t%ld\t%d\t%[^\n\f]",
886       &symnum, tag, seq, dfpos, dlnum, fname);
887     *cont = ftell(db_filep(db));
888   }
889 
890   /* work out file name if . abbreviation used, and possibly define file */
891   if( StringEqual(fname, AsciiToFull(".")) )
892   { StringCopy(fname, string(db));
893   }
894   *dfnum = FileNum(fname, DATA_SUFFIX);
895   if( *dfnum == NO_FILE )  /* can only occur in cross reference database */
896   { debug0(DFS, D, "  calling DefineFile from DbRetrieveNext");
897     *dfnum = DefineFile(fname, DATA_SUFFIX, &fpos(db),
898       DATABASE_FILE, SOURCE_PATH);
899   }
900   NumToSym(db, symnum, *sym);
901 
902   /* return */
903   debug3(DBS, DD, "DbRetrieveNext returning TRUE (in %s at %ld, line %d)",
904     FileName(*dfnum), *dfpos, *dlnum);
905   ifdebug(DPP, D, ProfileOff("DbRetrieveNext"));
906   return TRUE;
907 } /* end DbRetrieveNext */
908