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