Code and documentation copyright 1998 by Norman Ramsey. All rights reserved.
To compile this, you'll need Lua.
<*>= #include <string.h> #include <stdio.h> #include <stdlib.h> #include <assert.h> #include "pdbconv.h" #include "pdbrep.h" #include "pdblua.h" <macros> <functions>
These macros help pack and unpack Lua.
<macros>= (<-U)
#define UNFLAG(X) fprintf(out, " %s = %s,\n", #X, db->info.X ? "1" : "nil");
#define UNINT(X) fprintf(out, " %s = %d,\n", #X, db->info.X);
#define UNLINT(X) fprintf(out, " %s = %ld,\n", #X, db->info.X);
#define UNSTRING(X) fprintf(out, " %s = %s,\n", #X, QUOTE(db->info.X));
#define UNDATE(X) (fprintf(out, " %s = ", #X), \
unpack_date(out, db->info.X ## Date), \
fprintf(out, ",\n"));
#define PFLAG(X) info->X = bitval("Database." #X);
#define PINT(X) info->X = intval("Database." #X);
#define PINTOPT(X,N) info->X = intopt("Database." #X, N);
#define PLINT(X) PINT(X);
#define PLINTOPT(X, N) PINTOPT(X,N);
#define PSTRING(X) setstring(info->X, "Database." #X, sizeof(info->X)-1);
/* note size doesn't include terminating null */
#define PDATE(X) info->X ## Date = pack_date(objectval("Database.dates." #X));
DefinesPDATE,PFLAG,PINT,PINTOPT,PLINT,PLINTOPT,PSTRING,UNDATE,UNFLAG,UNINT,UNLINT,UNSTRING(links are to index).
<functions>= (<-U) [D->]
static PDBConverter get_converter(struct pdb *db); /* uses only type, creator */
static void dbLua(FILE *out, struct pdb *db);
static void unpack(FILE *in, FILE *out) {
struct pdb db = read_db(in);
dbLua(out, &db);
}
static void dbLua(FILE *out, struct pdb *db) {
PDBConverter convert = get_converter(db);
int i;
lua_beginblock();
fprintf(out, "-- This %s created by Norman Ramsey's pdb converter\n",
convert->application ? convert->application :
"generic view of a PDB file");
fprintf(out, "-- To use the output, get http://www.tecgraf.puc-rio.br/lua\n");
fprintf(out, "-- For more information, see http://www.cs.tufts.edu/~nr/pilot\n");
fprintf(out, "\n\n");
fprintf(out, "-- The value `Database' represents the database\n");
fprintf(out, "Database = {\n");
fprintf(out, " -- the first four fields are required\n");
UNSTRING(name)
UNSTRING(type) UNSTRING(creator)
UNINT(version)
fprintf(out, "\n -- no flags are required (default to nil)\n");
UNFLAG(resource) UNFLAG(readonly) UNFLAG(dirtyAppInfo)
UNFLAG(backup) UNFLAG(installOver) UNFLAG(reset)
{ static char *docs [] = {
"Date format:", " unixtime is seconds since 1/1/1970.",
" If unixtime is present, all other fields are ignored.",
" Otherwise, as many fields as are present are used to construct",
" a date, with missing fields defaulting to minimum values.",
" The value `today' (without quotes!) may be used instead",
" of a table; it is bound to a table with suitable unixtime",
0 };
putc('\n', out);
write_docs(out, 4, docs);
}
fprintf(out, " dates = {\n");
UNDATE(create) UNDATE(modify) UNDATE(backup)
fprintf(out, " },\n\n");
fprintf(out, " -- these next three fields default to 0 if omitted\n");
UNLINT(uidSeed)
fprintf(out, " -- nextRecordListID should always be 0\n");
UNLINT(nextRecordListID)
fprintf(out, " -- modnum is incremented at every database change\n");
UNLINT(modnum)
fprintf(out, " -- the following value exists just so we can do exact copies;\n");
fprintf(out, " -- it's fine to leave it out, and it defaults to zero\n");
fprintf(out, " filler = %d,\n", db->filler);
fprintf(out, "\n\n -- Everything from here down is application-dependent\n");
if (db->info.appInfo.chars && convert->appInfo.docs)
write_docs(out, 4, convert->appInfo.docs);
fprintf(out, " appInfo = ");
if (db->info.appInfo.chars)
convert->appInfo.unpack(out, db, db->info.appInfo);
else
fprintf(out, "nil");
fprintf(out, ",\n");
if (db->info.sortInfo.chars && convert->sortInfo.docs)
write_docs(out, 2, convert->sortInfo.docs);
fprintf(out, " sortInfo = ");
if (db->info.sortInfo.chars)
convert->sortInfo.unpack(out, db, db->info.sortInfo);
else
fprintf(out, "nil");
fprintf(out, ",\n");
{ static char *docs [] = {
"Records:", " records must be an array, and each item a table.",
" All fields except data can be omitted and default to 0 (or nil).",
" delete = deleted busy = locked by an app",
" secret = need passwd dirty = set at modify (cleared when?)",
0 };
write_docs(out, 2, docs);
if (convert->record.docs) {
fprintf(out, " -- The interpretation of the data field is as follows:\n");
write_docs(out, 6, convert->record.docs);
}
}
#define UNRFLAG(X) fprintf(out, "%s = %s, ", #X, db->records[i].X ? "1" : "nil");
fprintf(out, " records = {\n");
for(i=0; i<db->info.numrecs; i++) {
lua_beginblock();
fprintf(out, " { category = %d, uid = %d,\n ",
db->records[i].category, db->records[i].uid);
UNRFLAG(secret) UNRFLAG(busy) UNRFLAG(dirty) UNRFLAG(delete)
fprintf(out, "\n data = ");
convert->record.unpack(out, db, i);
fprintf(out, "\n }%s\n", comma(i + 1 < db->info.numrecs));
lua_endblock();
}
fprintf(out, " }\n");
fprintf(out, "}\n\n");
lua_endblock();
if (convert->trailers) {
char **p;
for (p = convert->trailers; *p; p++)
fprintf(out, "%s\n", *p);
}
}
DefinesdbLua,unpack,UNRFLAG(links are to index).
<functions>+= (<-U) [<-D->]
static void pack(char *filename, FILE *output) {
int i;
struct pdb db;
struct DBInfo *info = &db.info;
long startpos = ftell(output);
memset(&db, 0, sizeof(db)); /* trailing 0s for strings */
<set up the Lua data space and read in the database>
lua_beginblock();
<pack the Lua database into data structures, then write it>
lua_endblock();
}
Definespack(links are to index).
We set up a date of today.
<set up the Lua data space and read in the database>= (<-U) [D->]
{ static char buf[80];
sprintf(buf, "today = { unixtime = '%lu' }", time(NULL));
if (lua_dostring(buf)) assert(0);
}
We need this function to be able to count the number of records.
<bindlistlengthandistableinto the Lua space>= (U->) if(lua_dostring( "$debug\n" "function listlength(list, file, line)\n" " local i, n, v\n" " if type(list) ~= 'table' then \n" " error(file .. ':' .. tostring(line) .. ' -- value of type '" " .. type(list) .. ' is not a list') \n" " end\n" " i = nil\n" " n = 0\n" " repeat\n" " i, v = next(list, i)\n" " if (i ~= nil) then\n" " n = n + 1\n" " end\n" " until (i == nil)\n" " return n\n" "end\n" "\n" "function istable(x)\n" " return type(x) == 'table'\n" "end\n") ) assert(0);
<set up the Lua data space and read in the database>+= (<-U) [<-D]
if (lua_dofile(filename)) {
fprintf(stderr, "Could not read %s\n", filename ? filename : "<stdin>");
exit(1);
}
<pack the Lua database into data structures, then write it>= (<-U) [D->]
PSTRING(name) PSTRING(type) PSTRING(creator)
PINT(version)
PFLAG(resource) PFLAG(readonly) PFLAG(dirtyAppInfo)
PFLAG(backup) PFLAG(installOver) PFLAG(reset)
PDATE(create) PDATE(modify) PDATE(backup)
PLINTOPT(uidSeed, 0) PLINTOPT(nextRecordListID, 0) PLINTOPT(modnum, 0)
info->numrecs = intval("listlength(Database.records)");
db.filler = intopt("Database.filler", 0);
Definesbackup,create,name,resource,version(links are to index).
Next, compute the sizes of the applicatino info and sort areas, and advance the offset to the start of those areas.
<pack the Lua database into data structures, then write it>+= (<-U) [<-D->]
{ int offset = 78; /* size of database header */
PDBConverter convert = get_converter(&db);
int appsize = convert->appInfo.pack(NULL, 1);
int sortsize = convert->sortInfo.pack(NULL, 0);
int appoff, sortoff;
offset += info->numrecs * 8; /* sizeof record list */
offset += 2; /* size of filler */
Now we can computer the offsets.
<pack the Lua database into data structures, then write it>+= (<-U) [<-D->]
if (appsize) {
appoff = offset;
offset += appsize;
} else
appoff = 0;
if (sortsize) {
sortoff = offset;
offset += sortsize;
} else
sortoff = 0;
Given those offsets, write the database header part.
<pack the Lua database into data structures, then write it>+= (<-U) [<-D->]
if (write_dbinfo(output, info, appoff, sortoff) != 78)
assert(((void)"header length botch", 0));
Next, write the record list.
offset now points to the beginning of the first record.
<pack the Lua database into data structures, then write it>+= (<-U) [<-D->]
db.records = malloc(info->numrecs * sizeof(*db.records));
assert(db.records);
if (info->numrecs < 1) {
fprintf(stderr, "Cannot write database with %d records\n", info->numrecs);
exit(1);
}
for(i=0; i < info->numrecs; i++) {
lua_beginblock();
lua_pushnumber((double)(i+1)); lua_setglobal("i");
db.records[i].category = intopt("Database.records[i].category", 0);
db.records[i].uid = intopt("Database.records[i].uid", 0);
#define PRFLAG(F) db.records[i].F = bitval("Database.records[i]." #F);
PRFLAG(secret) PRFLAG(busy) PRFLAG(dirty) PRFLAG(delete)
write_record_header(output, &db.records[i], offset);
db.records[i].data = text(NULL, offset); /* flagrant misuse */
offset += convert->record.pack(NULL, i);
lua_endblock();
}
DefinesPRFLAG(links are to index).
Now we have written the header and record list, and it's time to write the filler.
<pack the Lua database into data structures, then write it>+= (<-U) [<-D->]
put_short(output, intopt("Database.filler", 0));
Next, write the app info and sort area.
<pack the Lua database into data structures, then write it>+= (<-U) [<-D->]
if (appoff) {
assert(ftell(output) - startpos == appoff);
convert->appInfo.pack(output, 1);
}
if (sortoff) {
assert(ftell(output) - startpos == sortoff);
convert->sortInfo.pack(output, 1);
}
At this point, we should be ready to write the records, and then we are done.
<pack the Lua database into data structures, then write it>+= (<-U) [<-D]
for (i = 0; i < info->numrecs; i++) {
assert(ftell(output) - startpos == db.records[i].data.len);
lua_beginblock();
convert->record.pack(output, i);
lua_endblock();
}
}
<functions>+= (<-U) [<-D->]
void usage(char *prog) {
fprintf(stderr, "Usage: %s [-generic] pack [input [output.pdb]] -- convert ASCII to .pdb\n", prog);
fprintf(stderr, " %s [-generic] unpack [input.pdb [output]] -- convert .pdb to ASCII\n", prog);
fprintf(stderr, " Omitted files default to stdin/stdout\n");
exit(1);
}
Definesusage(links are to index).
This is an error-checking fopen.
<functions>+= (<-U) [<-D->]
static FILE *openfile(char *name, char *mode) {
FILE *fp = fopen(name, mode);
if (fp) return fp;
fprintf(stderr, "Unable to open file %s in mode \"%s\"\n", name, mode);
exit(1);
return NULL; /* prevent warning */
}
Definesopenfile(links are to index).
<functions>+= (<-U) [<-D->]
extern struct PDBConv handyShopper, JFile, mobileDB, ToDo, expense;
static int generic = 0;
static PDBConverter converters[] = {
&handyShopper,
&JFile,
&mobileDB,
&ToDo,
&expense,
0
};
static PDBConverter get_converter(struct pdb *db) {
PDBConverter *convert;
for (convert = converters; !generic && *convert; convert++)
if (!strcmp((*convert)->type, db->info.type)
&& !strcmp((*convert)->creator, db->info.creator)
)
return *convert;
return generic_convert;
}
Definesconverters,generic,get_converter(links are to index).
<functions>+= (<-U) [<-D]
int main(int argc, char *argv[]) {
char *in, *out;
extern void strlib_open(void);
extern void iolib_open(void);
generic = argc > 1 && !strcmp(argv[1], "-generic");
atexit(showall);
strlib_open();
iolib_open();
<bind listlength and istable into the Lua space>
if (generic) { argv[1] = argv[0]; argv++; argc--; }
if (argc < 2 || argc > 4) usage(argv[0]);
in = argc < 3 ? NULL : strcmp(argv[2], "-") ? argv[2] : NULL;
out = argc < 4 ? NULL : strcmp(argv[3], "-") ? argv[3] : NULL;
if (!strcmp(argv[1], "pack"))
pack(in, out ? openfile(out, "wb") : stdout);
else if (!strcmp(argv[1], "unpack"))
unpack(in ? openfile(in, "rb") : stdin, out ? openfile(out, "w") : stdout);
else
usage(argv[0]);
return 0;
}
Definesmain(links are to index).
listlength and istable into the Lua space>: D1, U2