/* File: parse.c * Author: Richard Durbin (rd@mrc-lmb.cam.ac.uk) * Copyright (C) J Thierry-Mieg and R Durbin, 1992 * ------------------------------------------------------------------- * Acedb is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if n t, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * or see the on-line version at http://www.gnu.org/copyleft/gpl.txt * ------------------------------------------------------------------- * This file is part of the ACEDB genome database package, written by * Richard Durbin (MRC LMB, UK) rd@mrc-lmb.cam.ac.uk, and * Jean Thierry-Mieg (CRBM du CNRS, France) mieg@kaa.cnrs-mop.fr * * Description: ace file parser * Exported functions: parseControl, parseFile, parseBuffer. * HISTORY: * Last edited: Jan 22 10:42 2002 (edgrif) * * Jul 6 16:16 2000 (edgrif): Fixed a number of bugs, reporting of * all errors for pparse broken, reporting of final stats * for xace "readall" parsing broken etc. etc. * * Jan 6 09:28 2000 (edgrif): Fix for SANgc03115. * * Oct 28 10:22 1999 (fw): avoid error when parsing class Tag * which may still be present in ACE4 dump files. * * Oct 13 16:04 1999 (fw): parsing only possible from ACEIN * this only leaves doParseLevel and gets rid of * parsePipe, parseFile etc. * * May 12 15:19 1993 (mieg): added protected mode * * Apr 26 15:31 1993 (cgc): removed -T Top option * * Apr 25 23:25 1993 (cgc): added -R replace option: relpos, _bsHere * * Jun 5 16:18 1992 (mieg): added NON_GRAPHIC to parse in non-graphic mode * * Mar 4 02:48 1992 (rd): added -R option for renaming objects * Created: Sun Jan 12 17:52:35 1992 (rd) * CVS info: $Id: parse.c,v 1.58 2004/04/09 22:21:46 mieg Exp $ *------------------------------------------------------------------- */ #include #include #include /* for arraykill */ #include #include #include #include #include #include /* for queryCheckConstraints() */ #include /* for sessionUserKey() */ #include #include /******** extra stuff for graphical parse-window **********/ /* */ #ifndef NON_GRAPHIC #include #include #include #ifdef ACEDB_GTK #include #endif /* ACEDB_GTK */ static Graph parseGraph = 0 ; static void readAll (void); static void parseDraw(void); static void parseDispDestroy(void); static void openFile(void); static void closeFile(void); static void ignoreErrors(void) ; static void readItem(void); static void readLine(void); static void skipItem(void); #ifdef ACEDB_GTK static void parseDispEnterText(void); #endif /* ACEDB_GTK */ static MENUOPT parseOptsOpen[] = { { openFile, "Open ace File"}, #if defined (ACEDB_GTK) { parseDispEnterText,"Enter text"}, #endif /* UNIX */ { graphDestroy, "Quit"}, {0,0} } ; #define IGNORE_ERRORS_TXT "Ignore Errors" #define STOP_ERRORS_TXT "Stop on Error" #define ERROR_INDEX 1 static MENUOPT parseOptsClose[] = { { closeFile, "Close File"}, { ignoreErrors, IGNORE_ERRORS_TXT}, #if defined (ACEDB_GTK) { readAll, "Read all"}, { graphForceInterrupt ,"Stop"}, #elif defined(MACINTOSH) { readAll, "Read all"}, /* MAC */ #elif defined(WIN32) { readAll, "Read all (Use \"Esc\" to break in)"}, /* WINDOWS */ #else { readAll, "Read all (hit key F4 to break in)"}, /* UNIX-X11 */ #endif /* UNIX */ { readItem, "Read one Item"}, { readLine, "Read one Line"}, { skipItem, "Skip to next Item"}, {0,0} } ; #endif /* !NON_GRAPHIC */ /***********************************************************************/ /************* globals exported from this module ***************/ ParseFuncType parseFunc[256] ; /* == MAXTABLE of lexsubs.c */ /***********************************************************************/ /********** stuff local to the parser routine and initialiser **********/ static magic_t PF_MAGIC = "PF" ; static char *PARSE_IN_SPECIAL = "\n/\\\t@"; /* \n - end-of-line * // - comments * \ - escape char * \t - 8 spaces * @ - commandfile */ typedef enum { DONE, OUTSIDE, INSIDE, REF_BLOCKED } ParseStateType; #ifndef NON_GRAPHIC typedef enum { READ_ALL=1, READ_ITEM, READ_LINE } ParseUpdateModeType; #endif /* !NON_GRAPHIC */ /* Note that the enum and string version arrays must be kept in step. */ typedef enum {PARSE_NO_UPDATE, PARSE_GENERAL_ERR, PARSE_ARRAY_ERR, PARSE_OBJECT_ERR} ParseErrType ; char *parse_err_typestrings[4] = {"update", "general", "array", "object"} ; typedef struct ParseStruct { magic_t *magic; /* == &PF_MAGIC */ ACEIN parse_in ; char *filename; ParseStateType state ; /* if DONE it's not parsing and shouldn't have a parse_in */ BOOL isError ; /* parse vs. pparse */ /* Stats on object parsing: */ /* */ /* nob = number of paragraphs (= objects) in the ace data input */ /* (in theory nob = nok + nerr) */ /* */ /* nok = nadded + neditted + ndeleted + naliased + nrenamed */ /* + narray_added + narray_empty + narray_deleted */ /* */ /* nerr = ngen_err + nerror + narray_err */ /* */ /* I am treating arrays differently because its not really possible to */ /* say for all arrays whether they are new, edits of existing arrays or */ /* what...its a bit inconsistent at the moment. */ BOOL full_stats ; /* TRUE means show everything. */ /* total parse stats. */ int nob ; int nok ; int nerr ; /* General errors */ int ngen_err ; /* normal object stats */ int nadded ; int neditted ; int ndeleted ; int naliased ; int nrenamed ; int nerror ; /* array object stats, note how semantics are different from normal obj. */ int narray_added ; int narray_empty ; int narray_deleted ; int narray_err ; char *obname ; BOOL new_obj ; /* Is the object a new object */ char *obj_firstline ; /* Keep record of first line of ojb. for error messages. */ int curr_line ; BOOL parseBusy ; BOOL doParseProtected; /* TRUE => doing a kernel parse. */ BOOL parseKeepGoing ; /* Controls parsing behaviour: */ /* if FALSE stops parsing on 1st error */ BOOL parseLineErrors ; /* Controls error output: */ /* if FALSE then individual line errors are not reported. */ BOOL parseQuiet ; /* TRUE => no message are output. */ ACEOUT output ; /* alternative destination for error messages. */ /* It's possible for files to get extraneous null chars in them sometimes, */ /* this will lead to premature end of parsing with no real warning to the */ /* user. We trap this by looking at file lengths. */ BOOL has_length ; long int file_length ; long int file_pos ; /* the following are used only in parseLine() */ int recursion ; BOOL isDelete, isRename, isAlias, isBang ; OBJ obj ; KEY ref, timeStampKey ; BOOL save_error ; /* Signals error on trying to save object after rename etc. */ #ifndef NON_GRAPHIC char *currentLineText; ParseUpdateModeType updateMode ; /* determines which parts of the * display are updated during parse */ #endif /* !NON_GRAPHIC */ } *PF ; /* defines how parseLine should procede next. */ typedef enum _ParseNext {PARSE_SKIP, PARSE_NOSKIP, PARSE_ABORT} ParseNext ; static PF pfCreate (ACEIN parse_in, STORE_HANDLE handle); static void pfFinalise (void *block); #define pfDestroy(pf) messfree(pf) static BOOL pfExists (PF pf); static BOOL realParseAceIn(ACEIN parse_in, ACEOUT output, KEYSET ks, BOOL keepGoing, BOOL full_stats, BOOL silent, BOOL parse_protected) ; static BOOL doParseLevel (PF pf, KEYSET ks); static BOOL parseArray(PF pf, KEY key, int classe, char **err_text) ; static void parseLine(PF pf, ParseNext parse_type, KEYSET ks); static BOOL saveObject(PF pf, KEYSET ks, char **errtext) ; #ifndef NON_GRAPHIC /* */ /* Graphical routines, these should all be done by callback etc. etc. sigh...*/ /* */ static magic_t ParseDisp_MAGIC = "ParseDisp" ; typedef struct ParseDispStruct { magic_t *magic; /* == &ParseDisp_MAGIC */ PF pf; int buttonBox ; int lineBox, itemBox, nparsedBox, nokBox, nerrorBox ; char itemText[64], lineText[64], nparsedText[64], nokText[64], nerrorText[64] ; char dirSelection[DIR_BUFFER_SIZE]; char fileSelection[FIL_BUFFER_SIZE]; } *ParseDisp; static ParseDisp currentParseDisp (char *caller); static BOOL parseDispExists (ParseDisp pdisp); static void showState(ParseDisp pdisp); #endif /* !NON_GRAPHIC */ /****************************************************************** ************************* public functions *********************** ******************************************************************/ /* The new parseAceIn function which allows the caller to specify an */ /* alternative destination for output messages, this is especially needed */ /* by the server. */ /* will destroy parse_in */ BOOL doParseAceIn(ACEIN parse_in, ACEOUT output, KEYSET ks, BOOL keepGoing, BOOL full_stats) { BOOL ok = FALSE ; ok = realParseAceIn(parse_in, output, ks, keepGoing, full_stats, FALSE, FALSE) ; /* will destroy parse_in */ return ok ; } /* parseAceIn */ BOOL parseBuffer(char *text, KEYSET ks, BOOL keepGoing) /* convenience function to parse a text-buffer */ { BOOL ok = FALSE ; ACEIN parse_in; if (!text || !*text) messcrash("parseBuffer() - received NULL or empty text-pointer"); parse_in = aceInCreateFromText (text, "", 0); ok = realParseAceIn(parse_in, NULL, ks, keepGoing, FALSE, FALSE, FALSE) ; /* will destroy parse_in */ return ok ; } /* parseBuffer */ /* This variation is used by code that silently creates and parses some ace */ /* data to fulfill some other function and hence does not want to display */ /* the usual parse message. */ /* convenience function to parse a text-buffer */ BOOL parseBufferInternal(char *text, KEYSET ks, BOOL keepGoing) { BOOL ok = FALSE ; ACEIN parse_in; if (!text || !*text) messcrash("parseBuffer() - received NULL or empty text-pointer"); parse_in = aceInCreateFromText(text, "", 0); ok = realParseAceIn(parse_in, NULL, ks, keepGoing, FALSE, TRUE, FALSE) ; /* will destroy parse_in */ return ok ; } /* parseBuffer */ /* This variation is used by AceC/acinside */ BOOL parseAceCBuffer (char *text, Stack s, ACEOUT given_out, KEYSET ks) { BOOL ok = FALSE ; ACEIN parse_in ; ACEOUT parse_out ; BOOL keepGoing = TRUE ; if (!text || !*text) messcrash("parseBuffer() - received NULL or empty text-pointer"); parse_in = aceInCreateFromText(text, "", 0); if (s) parse_out = aceOutCreateToStack (s, 0) ; else parse_out = given_out ; ok = realParseAceIn(parse_in, parse_out, ks, keepGoing, FALSE, TRUE, FALSE) ; /* will destroy parse_in */ if (s) aceOutDestroy(parse_out) ; return ok ; } /* parseAceCBuffer */ /* This old function has been recoded to be simply a call to the new */ /* doParseAceIn function with the "output" parameter set to NULL. */ /* */ /* DON'T USE THIS FUNCTION, USE doParseAceIn INSTEAD. */ /* */ /* will destroy parse_in */ BOOL parseAceIn(ACEIN fi, KEYSET ks, BOOL keepGoing) { BOOL result ; result = realParseAceIn(fi, NULL, ks, keepGoing, FALSE, FALSE, FALSE) ; return result ; } /* this should be in the private header really....and be called uParse...etc.*/ /* mustn't be called from user-code, only accessible to * kernel routines */ /* will destroy parse_in */ BOOL parseAceInProtected (ACEIN parse_in, KEYSET ks) { BOOL ok = FALSE ; /* in the future we should check that parse_in is server-side, * so no user-data can be read into protected classes, only * files likes constraints.wrm which should only be accessible * by the server's administrator */ ok = realParseAceIn(parse_in, NULL, ks, FALSE, FALSE, TRUE, TRUE) ; return ok ; } /* parseAceInProtected */ /************************************************************/ /* This is the function that most of the above calls actually end up going */ /* through, this stucture enables us to avoid callers having to set parse */ /* state data. */ /* Will destroy parse_in */ /* */ static BOOL realParseAceIn(ACEIN parse_in, ACEOUT output, KEYSET ks, BOOL keepGoing, BOOL full_stats, BOOL silent, BOOL parse_protected) { PF pf ; BOOL ok = FALSE ; /* Create our global parse control block and initialise it with callers */ /* parameters. */ pf = pfCreate(parse_in, 0) ; pf->output = output ; pf->parseKeepGoing = keepGoing ; pf->full_stats = full_stats ; if (silent) { pf->parseLineErrors = FALSE ; pf->parseQuiet = TRUE ; } pf->doParseProtected = parse_protected ; ok = doParseLevel(pf, ks); pfDestroy (pf) ; return ok ; } /* realParseAceIn */ #ifndef NON_GRAPHIC /* Main function for xace version of file parsing. */ /* */ void parseControl (void) { if (!checkWriteAccess()) return ; /* getMutex */ if (graphActivate (parseGraph)) graphPop () ; else { ParseDisp pdisp; STORE_HANDLE handle = handleCreate(); parseGraph = displayCreate ("DtAce_Parser"); pdisp = messalloc (sizeof(struct ParseDispStruct)); pdisp->magic = &ParseDisp_MAGIC; pdisp->itemBox = 0; pdisp->lineBox = 0; pdisp->nparsedBox = pdisp->nokBox = pdisp->nerrorBox = 0 ; if (getenv ("ACEDB_DATA")) strcpy (pdisp->dirSelection, getenv("ACEDB_DATA")) ; else if (dbPathStrictFilName("rawdata", "", "", "rd", handle)) strcpy (pdisp->dirSelection, dbPathStrictFilName ("rawdata", "", "", "rd", handle)) ; else /* set to root db-dir */ strcpy (pdisp->dirSelection, dbPathMakeFilName("", "", "", handle)); handleDestroy(handle); graphAssociate (&ParseDisp_MAGIC, pdisp); graphTextBounds (60, 20) ; /* needed for text box sizing */ graphRegister (DESTROY, parseDispDestroy) ; parseDraw () ; } /* releaseMutex */ return ; } /* parseControl */ #else /* NON_GRAPHIC */ /* currently the gifaceserver links in the .ng.o version of this file * but the rest of the gifaceserver is graphical code, so we have to * supply two stubs here to make the link possible. All this is horrible * and needs cleaning up */ /*void parseOneFile (FILE *fil, KEYSET ks) {;}*/ void parseControl (void) {;} #endif /* NON_GRAPHIC */ /****************************************************************** ************************ private functions *********************** ******************************************************************/ /* constructor */ static PF pfCreate (ACEIN parse_in, STORE_HANDLE handle) { PF pf; pf = (PF) halloc (sizeof (struct ParseStruct), handle) ; blockSetFinalise (pf, pfFinalise); pf->magic = &PF_MAGIC ; pf->doParseProtected = FALSE; /* mustn't be overridden by normal usercode */ /* All these can be overridden to change error reporting behaviour. */ pf->parseKeepGoing = FALSE ; pf->output = NULL ; pf->parseLineErrors = TRUE ; pf->parseQuiet = FALSE ; pf->full_stats = FALSE ; pf->nob = pf->nok = pf->nerr = pf->ngen_err = pf->nadded = pf->neditted = pf->ndeleted = pf->naliased = pf->nrenamed = pf->nerror = pf->narray_added = pf->narray_empty = pf->narray_err = pf->narray_deleted = 0 ; pf->state = DONE ; pf->save_error = FALSE ; pf->parse_in = parse_in; aceInSpecial (pf->parse_in, PARSE_IN_SPECIAL); pf->filename = strnew (aceInGetURL(parse_in), 0); pf->curr_line = 0 ; /* Can we take the length of the stream ? */ if (aceInStreamLength(pf->parse_in, &(pf->file_length))) pf->has_length = TRUE ; else pf->has_length = FALSE ; pf->file_pos = 0 ; return pf; } /* pfCreate */ static void pfFinalise (void *block) { PF pf = (PF)block; if (!pfExists(pf)) messcrash("pfFinalise() - received invalid block pointer"); if (pf->state != DONE) parseLine(pf, PARSE_ABORT, 0); /* closes open object & ace-file */ messfree(pf->filename); messfree(pf->obname); messfree(pf->obj_firstline) ; #ifndef NON_GRAPHIC messfree (pf->currentLineText); #endif /* !NON_GRAPHIC */ pf->magic = 0 ; return ; } /* pfFinalise */ static BOOL pfExists (PF pf) { if (pf && pf->magic == &PF_MAGIC) return TRUE; return FALSE; } /* pfExists */ /************************************************************/ /* does all parsing of array objects, these are special in that they do not */ /* appear in the models file but may be attached to bits of models data via */ /* keys with the same name as the class to which they are attached. */ /* All array stats incrementing is done here, note that error stats are */ /* recorded by calling routine. */ /* */ static BOOL parseArray(PF pf, KEY key, int classe, char **err_text) { BOOL result = FALSE ; if (classe <= 0 || classe >= 256) messcrash ("bad class %d passed to parseArray", classe) ; if (parseFunc[classe]) { /* a ParseFunc is registered */ ParseFuncReturn parsing ; *err_text = NULL ; parsing = parseFunc[classe](pf->parse_in, key, err_text) ; if (parsing == PARSEFUNC_OK) { /* we will need to update the stats here...rather than in the caller */ /* unless we return the parse result from here as well... */ /* BUT if we have seperate array stats we could just do them here. */ result = TRUE ; pf->narray_added++ ; } else if (parsing == PARSEFUNC_EMPTY) { result = TRUE ; pf->narray_empty++ ; } else { /* On failure, skip to end of object and report failure. */ while (aceInCard(pf->parse_in) && aceInWord(pf->parse_in)) ; /* cop out, really the functions should always return an error */ /* string if they fail. */ if (*err_text == NULL) *err_text = "registered function for parse of array data failed" ; pf->narray_err++ ; result = FALSE ; } aceInSpecial (pf->parse_in, PARSE_IN_SPECIAL); /* parseFunc may change the special */ } else { /* no ParseFunc registered, object not empty so skip over it. */ while (aceInCard(pf->parse_in) && aceInWord(pf->parse_in)) ; *err_text = messprintf("No parser available for array class %d", classe) ; pf->narray_err++ ; result = FALSE ; } return result ; } /* parseArray */ /*******************************************/ /* deals with one entry */ /* uummmm, this is not quite right, actually we dive in and out of this */ /* routine to parse a single entry. */ /* */ /* This routine calls certain other routines (lexDoAlias, graph stuff) that */ /* are not re-entrant and so must not be called again if the initial call to */ /* routine results in recursion. All such calls should be guarded with */ /* pf->recursion. */ /* */ static void parseLine(PF pf, ParseNext parse_type, KEYSET ks) { int table, ix ; float fx ; mytime_t maDate ; char *word, dummyChar; char *card = 0; KEY key, tag, type, relpos ; char *errtext = 0 ; char *objname = NULL ; #ifndef NON_GRAPHIC ParseDisp pdisp = currentParseDisp("parseLine"); #endif /* !NON_GRAPHIC */ #ifndef ACEDB4 extern BOOL READING_MODELS ; #endif ParseErrType err_type = PARSE_OBJECT_ERR ; /* commonest err is for non-array objects. */ pf->isError = FALSE ; /* Clear after error or skip. */ /* */ if (parse_type == PARSE_ABORT) { /* Current behaviour is to save an empty object if there is some tag */ /* error or something minor...don't konw if this is really a good idea.*/ goto done ; } else if (parse_type == PARSE_SKIP) { if (pf->state == DONE) goto done ; if (pf->state == OUTSIDE) return ; err_type = PARSE_NO_UPDATE ; goto abort ; } /*--------------------- Process an object -------------------------------*/ /* */ switch (pf->state) { /* Ace file is done (perhaps because of error) */ case DONE : goto done ; /* Look for next object */ case OUTSIDE : /* Find the next paragraph, i.e. the next potential object. */ pf->isBang = FALSE ; messfree(pf->obj_firstline) ; while (TRUE) { #ifndef NON_GRAPHIC messfree (pf->currentLineText); #endif /* !NON_GRAPHIC */ /* Record where we are in stream, note that by the time we get to */ /* here the file may already have been closed, which case we can't */ /* take its length so we cheat and make look like we got to the */ /* end. This is because of the aceIn packages horrible implicit */ /* close for aceInCard() when we reach the end of a file. */ if (pf->has_length) { if (pf->file_pos < pf->file_length) { if (!(aceInStreamPos(pf->parse_in, &(pf->file_pos)))) pf->file_pos = pf->file_length ; } } /* Note, the logic here is a bit tricky, if this fails then we are */ /* already at the end of the file and it has already been closed */ /* due to the yucky implicit close that the aceIn package does. */ if (!(card = aceInCard(pf->parse_in))) goto done ; #ifndef NON_GRAPHIC /* keep the current parse line for showState */ if (pf->updateMode == READ_LINE) pf->currentLineText = strnew (card, 0); #endif /* !NON_GRAPHIC */ pf->obj_firstline = strnew(card, 0) ; if (!pf->isBang) { word = aceInWord(pf->parse_in) ; if (word && *word != '!') /* Found a paragraph */ break ; if (word && *word == '!') /* jump paragraph */ pf->isBang = TRUE ; } else { word = aceInWord(pf->parse_in) ; if (!word) pf->isBang = FALSE ; /* end of jumped paragraph */ } } /* If we got here we must have found a potential object. */ (pf->nob)++ ; /* Special processing for a delete/rename/alias. */ pf->isDelete = pf->isRename = pf->isAlias = FALSE ; if (strcmp (word, "-D") == 0) /* symbol for deletion */ { pf->isDelete = TRUE ; if (!(word = aceInWord(pf->parse_in))) { errtext = "No class after -D" ; err_type = PARSE_GENERAL_ERR ; goto abort ; } } else if (strcmp (word, "-R") == 0) /* symbol for renaming */ { pf->isRename = TRUE ; if (!(word = aceInWord(pf->parse_in))) { errtext = "No class after -R" ; err_type = PARSE_GENERAL_ERR ; goto abort ; } } else if (strcmp (word, "-A") == 0) /* symbol for aliasing */ { pf->isAlias = pf->isRename = TRUE ; if (!(word = aceInWord(pf->parse_in))) { errtext = "No class after -A" ; err_type = PARSE_GENERAL_ERR ; goto abort ; } } /* Get hold of the objects class and name. */ /* */ messfree(pf->obname); pf->obname = NULL ; if (*word == '>') /* recognize > as fasta format for DNA */ { table = pickWord2Class("DNA") ; /* the FASTA-object name can appear directly glued * to the > character, or in the next word */ if (*(word + 1)) objname = word + 1 ; else objname = aceInWord (pf->parse_in) ; } else { table = pickWord2Class(word) ; /* word is the class name */ /* note - Could search classes to parse subclasses */ if ((pickList[table].type != 'A') && (pickList[table].type != 'B')) { errtext = messprintf ("Bad class name : %s", word ? word : "(NULL)") ; err_type = PARSE_GENERAL_ERR ; goto abort ; } if (!table) { errtext = messprintf("Unrecognised Class %s ", word) ; err_type = PARSE_GENERAL_ERR ; goto abort ; } if (pickList[table].protected && !pf->doParseProtected #ifdef ACEDB4 /* in ACEDB4 only, we avoid complaints about parsing the Tag class */ && strcmp(name(pickList[table].classe), "Tag") != 0 #endif ) { errtext = messprintf("Trying to read/alter a protected class %s", word) ; err_type = PARSE_GENERAL_ERR ; goto abort ; } #ifdef ACEDB4 /* old models */ if (pickList[table].type == 'B') /* a kludge to check existence of the model */ { if ((pf->obj = bsCreate (KEYMAKE (table,0)))) bsDestroy (pf->obj) ; else { errtext = messprintf ("Missing model of class %s", word) ; goto abort ; } } #else /* new models */ if (!READING_MODELS && pickList[table].type == 'B' && !pickList[table].model) { errtext = messprintf ("Missing model of class %s", word) ; goto abort ; } #endif /* ACEDB4 */ aceInStep(pf->parse_in, ':') ; /* optional colon after class */ /* get the object name */ objname = aceInWord(pf->parse_in) ; } if (!lexIsGoodName(objname)) { errtext = messprintf("Object name %s not valid", objname ? objname : "(NULL)") ; err_type = PARSE_GENERAL_ERR ; goto abort ; } /* we now have the class in 'table' and the object name in objname */ /* secure object name for showState */ pf->obname = strnew (objname, 0); /* Deal with rename or alias. */ if (pf->isRename) { pf->ref = 0 ; if (!lexword2key (pf->obname, &pf->ref, table)) { errtext = messprintf("Object %s not found in database", pf->obname) ; goto abort ; } /* Replace copy of original object name with copy of new name. */ messfree(pf->obname) ; if (!(pf->obname = aceInWord(pf->parse_in))) { errtext = messprintf("No new name for %s", pf->isAlias ? "alias (-A)" : "rename (-R)") ; goto abort ; } else pf->obname = strnew(pf->obname, 0) ; #ifndef NON_GRAPHIC switch (pf->updateMode) { case READ_ALL: if (!((pf->nob)%100)) showState (pdisp) ; if (!((pf->nob)%5000)) graphRedraw () ; break ; case READ_ITEM: case READ_LINE: showState (pdisp); break ; default: break ; } #endif /* !NON_GRAPHIC */ { char *err_msg = NULL ; (pf->recursion)++ ; if (pf->recursion >= 2) messcrash ("Multiple recursive call to lexalias inside parser") ; if (!lexDoAlias(pf->ref, word, FALSE, pf->isAlias, &err_msg)) { errtext = messprintf("%s, %s", (pf->isAlias ? "Alias failed" : "Rename failed"), err_msg) ; messfree(err_msg) ; (pf->recursion)-- ; goto abort ; } /* N.B. after rename we have a new object with a new key, */ /* we need to update pf->ref to reflect this, will fail for alias*/ if (!pf->isAlias) { pf->ref = 0 ; if (!lexword2key(pf->obname, &pf->ref, table)) { messcrash("%s, cannot get a key for new object \"%s\" in database", (pf->isAlias ? "Alias failed" : "Rename failed"), pf->obname) ; } } /* Make sure object is saved and keyset updated. */ /* I'm really not sure if its OK/necessary to save a */ /* renamed/aliased object, it all seems to come out in the wash..*/ if (!(saveObject(pf, ks, &errtext))) goto abort ; if (pf->isAlias) (pf->naliased)++ ; else (pf->nrenamed)++ ; pf->state = OUTSIDE ; (pf->recursion)-- ; } return ; } /* Deal with a delete. */ if (pf->isDelete) { if (!lexword2key(pf->obname, &pf->ref, table)) { errtext = messprintf("Object %s not found in database", pf->obname) ; goto abort ; } else { if (lexDestroyAlias (pf->ref)) /* true if this is an alias */ { /* Don't need to do anything here, just destroy the alias. */ } else { switch (pickType(pf->ref)) { case 'A': arrayKill(pf->ref) ; (pf->narray_deleted)++ ; break ; case 'B': if ((pf->obj = bsUpdate(pf->ref))) bsKill(pf->obj) ; (pf->ndeleted)++ ; break ; default: errtext = "Can't handle because unknown class type" ; err_type = PARSE_GENERAL_ERR ; goto abort ; } } pf->state = OUTSIDE ; return ; } } /* OK, must be a 'new' object definition. */ /* */ /* generate a key (pf->ref) for the object (pf->obname), which might */ /* be a new object to the database. */ pf->new_obj = lexaddkey(pf->obname, &(pf->ref), table) ; #ifndef NON_GRAPHIC /* just read another object - redraw screen */ switch (pf->updateMode) { case READ_ALL: if (!((pf->nob)%100)) showState (pdisp) ; if (!((pf->nob)%5000)) graphRedraw () ; break ; case READ_ITEM: case READ_LINE: showState (pdisp); break ; default: break ; } #endif /* !NON_GRAPHIC */ /* read timeStamp of new object */ pf->timeStampKey = 0; word = aceInWord(pf->parse_in); if (word && (strcmp(word, "-O") == 0 )) { if (!(word = aceInWord(pf->parse_in))) { errtext = "No timestamp after -O" ; goto abort ; } lexaddkey(word, &(pf->timeStampKey), _VUserSession); } switch (pickType (pf->ref)) { case 'A': if (ks) keySet (ks, keySetMax (ks)) = pf->ref ; if (parseArray(pf, pf->ref, table, &errtext)) return ; else { err_type = PARSE_ARRAY_ERR ; goto abort ; } case 'B': break ; /* handled by REF_BLOCKED */ default: errtext = "Can't handle because unknown class type" ; goto abort ; } pf->state = REF_BLOCKED ; /* note fall through */ case REF_BLOCKED : if (!(pf->obj = bsUpdate (pf->ref))) { errtext = messprintf ("Object %s is locked - free before continuing", pf->obname) ; goto abort ; } pf->state = INSIDE ; return ; /* Carry on processing current object. */ case INSIDE : #ifndef NON_GRAPHIC messfree (pf->currentLineText); #endif /* !NON_GRAPHIC */ /* Record where we are in stream, must do this before aceInCard */ /* because aceInCard closes file on EOF and we lose the position. */ if (pf->has_length) { if (pf->file_pos < pf->file_length) aceInStreamPos(pf->parse_in, &(pf->file_pos)) ; } pf->curr_line = aceInStreamLine(pf->parse_in) - 1 ; /* File finished, so save object and clear up. */ if (!(card = aceInCard(pf->parse_in))) { if (saveObject(pf, ks, &errtext)) { if (pf->new_obj) (pf->nadded)++ ; else (pf->neditted)++ ; goto done ; } else goto abort ; } #ifndef NON_GRAPHIC /* keep the current parse line for showState */ if (pf->updateMode == READ_LINE) pf->currentLineText = strnew (card, 0); #endif /* !NON_GRAPHIC */ /* blank line, so save object and go on to next one. */ if (!(word = aceInWord (pf->parse_in))) { if (!(saveObject(pf, ks, &errtext))) goto abort ; if (pf->new_obj) (pf->nadded)++ ; else (pf->neditted)++ ; pf->state = OUTSIDE ; return ; } /* More of object to process. */ if (*word == '!') return ; /* the 'word' is a tag name */ if (strcmp (word, "-D") == 0) /* symbol for deletion */ { pf->isDelete = TRUE ; if (!(word = aceInWord(pf->parse_in))) { errtext = "No tag after -D" ; goto abort ; } } else pf->isDelete = FALSE ; if (!lexword2key (word,&tag,0)) /* find tag */ { errtext = messprintf ("Unknown tag \"%s\"",word) ; goto abort ; } aceInNext(pf->parse_in); aceInStep (pf->parse_in, ':') ; /* optional colon after tag */ bsGoto(pf->obj, 0) ; /* pop out of subobjs * return to obj-root */ if (bsAddTag (pf->obj,tag)) /* subobjects come on the same line */ while ((word = aceInWord (pf->parse_in))) { scanSubObj: relpos = _bsRight ; if (*word == '-' && strlen(word) == 2) switch (word[1]) { case 'C': /* standard comment */ if (!(word = aceInWord(pf->parse_in))) { errtext = "No Comment after -C" ; goto abort ; } bsAddComment (pf->obj, word, 'C') ; if (!(word = aceInWord(pf->parse_in))) continue ; if (strcmp(word,"-O") != 0) /* only a timestamp possible */ { errtext = "Extra item on -C line" ; goto abort ; } /* NOTE: fall-through if -O after -C */ case 'O': /* timestamp (UserSession class) */ if (!(word = aceInWord(pf->parse_in))) { errtext = "No UserSession key after -O" ; goto abort ; } lexaddkey (word, &key, _VUserSession) ; bsSetTimeStamp (pf->obj, key) ; continue ; case 'R': /* Replace */ if (!(word = aceInWord(pf->parse_in))) continue ; relpos = _bsHere ; } if (!(type = bsType (pf->obj, relpos))) { errtext = messprintf ("tag \"%s\" : %s - off end of model\n", name (tag), word) ; goto abort ; } if (class(type) == _VQuery) break ; #ifndef ACEDB4 /* new models */ if (class(type) == _VModel) /* a type */ #else /* ACEDB4 */ /* old models */ if (class(type) && type == KEYMAKE(class(type), 1)) /* a type */ #endif /* ACEDB4 */ { if (!bsPushObj(pf->obj)) { errtext = messprintf ("tag \"%s\" : %s - off end of model, " "addObj failed\n", name(tag), word) ; goto abort ; } goto scanSubObj ; } #ifndef ACEDB4 if (class(type) && (table = type, pickIsA(&table, 0), table)) #else /* ACEDB4 */ if ((table = class(type))) #endif /* ACEDB4 */ { if (!lexIsGoodName(word)) /* refuse blank strings: " " */ { errtext = messprintf ("Blank object names names are illicit, sorry") ; goto abort ; } if (pickKnown (table) && !lexword2key (word,&key,table)) { errtext = messprintf ("tag \"%s\" : class: \"%s\" %s - unknown. " "You can not create an " "entry in a \"Known\" class in an indirect way (see " "options.wrm)\n", name(tag), pickClass2Word(table), word) ; goto abort ; } lexaddkey (word,&key,table) ; bsAddKey (pf->obj,relpos,key) ; } else if (type == _Int) { if (sscanf (word,"%d%c",&ix,&dummyChar) == 1) bsAddData (pf->obj,relpos,_Int,&ix) ; else { errtext = messprintf ("%s not an integer after %s\n", word, name(tag)) ; goto abort ; } } else if (type == _Float) { double bigfloat = 0 ; if (sscanf (word,"%lf%c",&bigfloat,&dummyChar) == 1 && bigfloat < 1e40 && bigfloat > -1e40 ) { char buf[128] ; if (bigfloat <= ACE_FLT_MIN && bigfloat >= -ACE_FLT_MIN) bigfloat = 0 ; sprintf (buf, "%1.7g", bigfloat) ; sscanf (buf, "%f", &fx) ; bsAddData (pf->obj,relpos,_Float,&fx) ; } else { errtext = messprintf ("%s not a float after %s\n", word,name(tag)) ; goto abort ; } } else if (type == _DateType) { if ((maDate = timeParse (word))) bsAddData (pf->obj,relpos,_DateType,&maDate) ; else { errtext = messprintf ("%s not a DateType after %s\n", word,name(tag)) ; goto abort ; } } else if (type < _LastC) bsAddData (pf->obj,relpos,type,word) ; else /* a tag */ { if (!lexword2key (word,&tag,0)) /* find tag */ { errtext = messprintf ("Unknown tag \"%s\"",word) ; goto abort ; } if (!bsAddTag(pf->obj, tag)) { errtext = messprintf ("Tag \"%s\" not in object",word) ; goto abort ; } } } else { errtext = messprintf ("Tag \"%s\" does not fit model\n", name(tag)) ; goto abort ; } if (pf->isDelete) bsPrune (pf->obj) ; bsGoto(pf->obj, 0) ; /* move obj-cursor back to root */ return ; } /* end-switch pf->state */ /*--------------------- Process an object (End) -------------------------*/ /* End processing if we hit an error or file is finished. */ /* */ abort: /* Report parsing errors: */ /* Note that errors are always logged. It's possible to turn off error */ /* reporting, don't know if this is ever used. */ if (errtext) { char *out_msg = NULL ; /* Prepare the message, print at most 50 chars of obj_firstline */ out_msg = hprintf(0, "%s parse error, near line %d while parsing \"%.50s\", error was: %s", parse_err_typestrings[err_type], aceInStreamLine(pf->parse_in)-1, pf->obj_firstline, errtext) ; /* If we have an output stream then send errors to it, rather than */ /* messerror, e.g. server uses this to send messages back to client. */ if (pf->output) { aceOutPrint(pf->output, "// %s\n", out_msg) ; /* N.B. the "// ", message could be going back to the client. */ messdump(out_msg) ; } else { if (pf->parseKeepGoing && !pf->parseLineErrors) messdump(out_msg) ; else messerror(out_msg) ; /* N.B. messerror DOES a messdump. */ } messfree(out_msg) ; } /* If save_error = TRUE it means we have just tried to save an object but */ /* failed, this means there is no need to skip to the end of the current */ /* object. Otherwise we do need to skip to the next object. */ if (!pf->save_error) { while (aceInCard(pf->parse_in)) { char *first_word ; /* Object definitions/edits must be separated by blank lines, */ /* rename, delete or alias do not have to be. */ if ((first_word = aceInWord(pf->parse_in))) { if ((strcmp(first_word, "-R") == 0) || (strcmp(first_word, "-D") == 0) || (strcmp(first_word, "-A") == 0)) { aceInCardBack(pf->parse_in) ; break ; } } else break ; } } bsDestroy (pf->obj) ; pf->state = OUTSIDE ; if (!pf->parseKeepGoing) pf->isError = TRUE ; /* parseArray() increments array stats, so only do errors for non-arrays. */ if (err_type == PARSE_GENERAL_ERR) (pf->ngen_err)++ ; else if (err_type == PARSE_OBJECT_ERR) (pf->nerror)++ ; #ifndef NON_GRAPHIC showState (pdisp) ; #endif /* NON_GRAPHIC */ return ; done: { saveObject(pf, ks, &errtext) ; /* finish this stream */ aceInDestroy (pf->parse_in); /* aceInClose (pf->fi, pf->level) ;*/ pf->state = DONE ; /* must come before showState */ /* Now that we're done we report the final state to the user */ /* Note, the recursion is private to the code, not for the end user */ #ifndef NON_GRAPHIC /* GRAPHIC */ if (!pf->recursion) parseDraw () ; #else /* NON_GRAPHIC */ /* NOTHING TO DO FOR NON-GRAPHICAL CODE, see doParseLevel */ #endif /* NON_GRAPHIC */ /* If parsing finished successfully we still need to check if it stopped */ /* part way through a file, this can happen if a NULL char has crept into*/ /* a file (e.g. because of an NFS glitch). NOTE if parse_in is pipe/stdin*/ /* etc. we can't take its length, if file_pos == -1, this says there was */ /* an error in finding position in file. Fix for SANgc03115 */ /* Note that this check is impossible to do correctly under cygwin, */ /* since the length and pos counts are affected by the translation of */ /* line endings from to . Since this check is really for */ /* NFS problems, which are irrelevant under windows, the check is not */ /* done under CYGWIN. */ #ifndef __CYGWIN__ #ifdef ED_G_NEVER_INCLUDE_THIS_CODE /* I have completely disabled this for now because of the following */ /* problem: */ /* The problem with the check is that it will fail if the last line in the input is not terminated with a "\n" because in this case the file/text is closed and the stream level decremented without the caller being able to get the final length of file/text parsed. In our case we do try to get the length but because the streamlevel is decremented to 0 by then, we get length zero returned for the file. This then screws up the below test. We could do a new aceInDoCard() that takes a BOOL param to specify whether to close the file implicitly. aceInCard() then become a macro which expanded to a call to aceInDoCard() This is a basic problem with the acein interface really. The only way round it is to provide a variant of aceInCard that allows one to specify that an implicit close is not done. This would be fairly easy to do but not a high priority right now I think. */ if (parse_type != PARSE_ABORT && pf->has_length) { if (pf->file_length != pf->file_pos) { messerror("Although parsing was successful, it stopped before the end of the file. " "This may be because NULL characters have accidentally " "been introduced into the file, these will cause the parser to think " "it has reached the end of the file and stop. " "File was %ld bytes long, parsing stopped after %ld bytes.", pf->file_length, pf->file_pos) ; } } #endif /* ED_G_NEVER_INCLUDE_THIS_CODE */ #endif /* !__CYGWIN__ */ return ; } return ; } /* parseLine */ /****************************************************************/ /* If an object has been renamed, editted or added, it needs to be saved, */ /* these are the steps to do it. */ /* Returns TRUE if the save could be done, FALSE if constaint checks failed. */ static BOOL saveObject(PF pf, KEYSET ks, char **errtext) { BOOL result = FALSE ; if (pickList[class(pf->ref)].constraints && !queryCheckConstraints (pf->obj, pf->ref, pickList[class(pf->ref)].constraints)) { *errtext = messprintf ("Object %s does not satisfy constraints", name (pf->ref)) ; pf->save_error = TRUE ; /* prevent jumping next object */ result = FALSE ; } else { /* add the object we've just parsed into the user-supplied keyset, */ /* this makes the "Active Objects" correct. */ if (ks) keySet (ks, keySetMax (ks)) = pf->ref ; bsSave (pf->obj) ; /* Only set the creation timestamp from the acefile if the object was created this session */ if (pf->timeStampKey && (lexCreationUserSession(pf->ref) == sessionUserKey())) lexSetCreationUserSession(pf->ref, pf->timeStampKey); result = TRUE ; } return result ; } /****************************************************************/ static BOOL doParseLevel(PF pf, KEYSET ks) { BOOL result = FALSE ; #ifdef ED_G_NEVER_INCLUDE_THIS_CODE int active_obj = 0 ; /* see comments below. */ #endif /* ED_G_NEVER_INCLUDE_THIS_CODE */ char *parse_msg = NULL ; if (!pfExists(pf)) messcrash("doParseLevel() - received invalid pf pointer"); pf->nob = pf->nok = pf->nerr = pf->ngen_err = pf->nadded = pf->neditted = pf->ndeleted = pf->naliased = pf->nrenamed = pf->nerror = pf->narray_added = pf->narray_empty = pf->narray_err = pf->narray_deleted = 0 ; pf->state = OUTSIDE ; do { parseLine (pf, PARSE_NOSKIP, ks); } while (!pf->isError && pf->state != REF_BLOCKED && pf->state != DONE); if (pf->state == DONE) { result = TRUE ; } else { result = FALSE ; /* closes open object & ace-file */ pf->state = DONE ; parseLine (pf, PARSE_ABORT, ks) ; } /* Summary of processing, note that for some processing, active_obj could */ /* be zero because ks is NULL, or it could be zero because no objects got */ /* put in the active keyset. Currently this all comes out in the wash, */ /* because ks is only NULL for non-user parsing (e.g. models file). */ if (ks) { keySetSort(ks) ; keySetCompress (ks) ; #ifdef ED_G_NEVER_INCLUDE_THIS_CODE /* I'm not sure we want the active object count, its displayed anyway */ /* at the end of each command. */ /* It can always be included later by uncommenting this and including */ /* the count in the stats below. */ active_obj = keySetCountVisible(ks) ; #endif /* ED_G_NEVER_INCLUDE_THIS_CODE */ } /* some global stats are derived from sub-stats. */ pf->nok = pf->nadded + pf->neditted + pf->ndeleted + pf->nrenamed + pf->naliased + pf->narray_added + pf->narray_empty + pf->narray_deleted ; pf->nerr = pf->ngen_err + pf->nerror + pf->narray_err ; if (pf->full_stats) parse_msg = hprintf(0, "total processed: %d found, %d parsed ok, %d parse failed\n" " general: %d errors\n" " objects: %d added, %d editted, %d deleted, %d renamed, %d aliased, %d errors\n" " arrays: %d added, %d empty, %d deleted, %d errors\n", pf->nob, pf->nok, pf->nerr, pf->ngen_err, pf->nadded, pf->neditted, pf->ndeleted, pf->nrenamed, pf->naliased, pf->nerror, pf->narray_added, pf->narray_empty, pf->narray_deleted, pf->narray_err) ; else parse_msg = hprintf(0, "objects processed: %d found, %d parsed ok, %d parse failed\n", pf->nob, pf->nok, pf->nerr) ; /* Only output the message if its _not_ a kernel parse (for model reading.)*/ if (!(pf->parseQuiet)) { if (pf->output) aceOutPrint(pf->output, "// %s\n", parse_msg) ; else messout(parse_msg) ; } messfree(parse_msg) ; return result ; } /* doParseLevel */ /*************** graphical functions ***********************/ #ifndef NON_GRAPHIC static ParseDisp currentParseDisp (char *caller) { ParseDisp pdisp = 0; if (!graphActivate (parseGraph)) return NULL; graphAssFind (&ParseDisp_MAGIC, &pdisp) ; if (!pdisp) return NULL; if (!parseDispExists(pdisp)) messcrash("%s() - found non-magic ParseDisp on graph", caller); return pdisp; } /* currentParseDisp */ static BOOL parseDispExists (ParseDisp pdisp) { if (pdisp && pdisp->magic == &ParseDisp_MAGIC) return TRUE; return FALSE; } /* parseDispExists */ static void readAll (void) /* parse-window function */ { ParseDisp pdisp = currentParseDisp("readAll"); BOOL orig_parseLineErrors ; if (!pdisp || !pdisp->pf || pdisp->pf->parseBusy) return; pdisp->pf->parseBusy = TRUE; parseDraw(); /* redraw screen in its busy state */ pdisp->pf->updateMode = READ_ALL ; #ifdef ACEDB_GTK graphBusyCursorAll(); #else /* !ACEDB_GTK */ graphBusyCursorAll(TRUE); #endif /* !ACEDB_GTK */ graphSetBlockMode(GRAPH_BLOCKING); /* Don't report multiple errors for graphical parsing (user can instead */ /* step through from error to error). */ orig_parseLineErrors = pdisp->pf->parseLineErrors ; pdisp->pf->parseLineErrors = FALSE ; pdisp->pf->isError = pdisp->pf->parseKeepGoing ; do { parseLine (pdisp->pf, PARSE_NOSKIP, 0); } while (!messIsInterruptCalled() && !pdisp->pf->isError && pdisp->pf->state != REF_BLOCKED && pdisp->pf->state != DONE) ; graphSetBlockMode(GRAPH_BLOCKABLE); pdisp->pf->parseLineErrors = orig_parseLineErrors ; pdisp->pf->parseBusy = FALSE; parseDraw (); /* redraw screen in its idle state */ /* are we finished totally ?? */ if (pdisp->pf->state == DONE) pfDestroy (pdisp->pf); return; } /* readAll */ static void readItem (void) /* parse-window function */ { ParseDisp pdisp = currentParseDisp("readItem"); if (!pdisp || !pdisp->pf || pdisp->pf->parseBusy) return; pdisp->pf->parseBusy = TRUE; pdisp->pf->updateMode = READ_ITEM ; do { parseLine (pdisp->pf, PARSE_NOSKIP, 0); } while (pdisp->pf->state == INSIDE) ; /* start parsing next item, so we have already the name to report * in the display */ parseLine (pdisp->pf, PARSE_NOSKIP, 0); pdisp->pf->parseBusy = FALSE; /* are we finished totally ?? */ if (pdisp->pf->state == DONE) pfDestroy (pdisp->pf); parseDraw (); return; } /* readItem */ static void readLine (void) { ParseDisp pdisp = currentParseDisp("readLine"); if (!pdisp || !pdisp->pf || pdisp->pf->parseBusy) return; pdisp->pf->parseBusy = TRUE; pdisp->pf->updateMode = READ_LINE ; parseLine (pdisp->pf, PARSE_NOSKIP, 0); pdisp->pf->parseBusy = FALSE; /* are we finished totally ?? */ if (pdisp->pf->state == DONE) pfDestroy (pdisp->pf); /* parseDraw ();*/ return; } /* readLine */ static void skipItem (void) /* parse-window function */ { ParseDisp pdisp = currentParseDisp("skipItem"); if (!pdisp || !pdisp->pf || pdisp->pf->parseBusy) return; pdisp->pf->parseBusy = TRUE; pdisp->pf->updateMode = READ_LINE ; parseLine (pdisp->pf, PARSE_SKIP, 0); /* skip one item */ parseLine (pdisp->pf, PARSE_NOSKIP, 0); pdisp->pf->parseBusy = FALSE; return; } /* skipItem */ #ifdef ACEDB_GTK static void parseDispEnterText(void) /* parse-window function */ { ParseDisp pdisp = currentParseDisp("parseDispEnterText"); char *buff; if (!pdisp || /* only refuse to parseDispEnterText, * if an existing parse-action is busy */ (pdisp->pf && pdisp->pf->parseBusy)) return; buff = gexTextEditor("Type text to be read", NULL, 0); if (buff) { parseBuffer(buff, 0, FALSE); messfree (buff); } return; } /* parseDispEnterText */ #endif /* ACEDB_GTK */ static void openFile (void) /* parse-window function */ { ACEIN parse_in; ParseDisp pdisp = currentParseDisp("openFile"); if (!sessionGainWriteAccess()) { /* may occur if somebody else grabbed it */ messerror ("You no longer have write access!"); graphDestroy () ; return ; } if (!pdisp || /* only refuse to openFile, * if an existing parse-action is busy */ (pdisp->pf && pdisp->pf->parseBusy)) return; /* destroy the old parse-actions, because we'll make a new one */ if (pdisp->pf) pfDestroy (pdisp->pf); /* write access may be lost doing a save */ parse_in = aceInCreateFromChooser ("Which ace file do you wish to parse ?", pdisp->dirSelection, pdisp->fileSelection, "ace", "r", 0); if (!parse_in) return ; pdisp->pf = pfCreate (parse_in, 0); aceInSpecial (pdisp->pf->parse_in, PARSE_IN_SPECIAL) ; messdump ("Parsing file %s", pdisp->pf->filename) ; pdisp->pf->state = OUTSIDE ; pdisp->pf->nob = pdisp->pf->nok = pdisp->pf->nerror = pdisp->pf->ngen_err = pdisp->pf->nadded = pdisp->pf->neditted = pdisp->pf->ndeleted = pdisp->pf->naliased = pdisp->pf->nrenamed = pdisp->pf->narray_added = pdisp->pf->narray_empty = pdisp->pf->narray_err = pdisp->pf->narray_deleted = 0 ; parseDraw () ; return; } /* openFile */ static void closeFile (void) /* parse-window function */ { ParseDisp pdisp = currentParseDisp("closeFile"); if (!pdisp || (pdisp->pf && pdisp->pf->parseBusy)) return; if (pfExists(pdisp->pf)) pfDestroy (pdisp->pf); /* kill current parse-action */ parseDraw () ; return; } /* closeFile */ /* Toggle error state. */ static void ignoreErrors(void) { ParseDisp pdisp = currentParseDisp("parseDispEnterText"); if (pdisp && pdisp->pf && !(pdisp->pf->parseBusy)) { pdisp->pf->parseKeepGoing = !(pdisp->pf->parseKeepGoing) ; if (pdisp->pf->parseKeepGoing) parseOptsClose[ERROR_INDEX].text = STOP_ERRORS_TXT ; else parseOptsClose[ERROR_INDEX].text = IGNORE_ERRORS_TXT ; pdisp->buttonBox = graphButtons (parseOptsClose, 1, 3, 55) ; if (!pdisp->pf->parseBusy) graphBoxClear (pdisp->buttonBox + 3); /* remove Stop button */ graphRedraw() ; } return ; } /********************************************/ static void parseDraw (void) /* parse-window function */ { int line = 9 ; ParseDisp pdisp = currentParseDisp("parseDraw"); if (!pdisp) return; graphPop () ; graphClear () ; if (pfExists(pdisp->pf)) /* we're parsing at the moment */ { if (pdisp->pf->filename && strlen(pdisp->pf->filename) > 0) /* show name of file being parsed */ { graphText ("Filename:", 1, 1); graphText (pdisp->pf->filename, 11,1); } pdisp->buttonBox = graphButtons (parseOptsClose, 1, 3, 55) ; if (!pdisp->pf->parseBusy) graphBoxClear (pdisp->buttonBox + 3); /* remove Stop button */ } else /* we're not parsing at the moment */ pdisp->buttonBox = graphButtons (parseOptsOpen, 1, 3, 55) ; graphText (" Item:",1,line) ; pdisp->itemBox = graphBoxStart () ; graphTextPtr (pdisp->itemText, 9, line, 64) ; graphBoxEnd () ; line += 1.5 ; graphText (" Line:",1,line) ; pdisp->lineBox = graphBoxStart () ; graphTextPtr (pdisp->lineText, 9, line, 64) ; graphBoxEnd () ; line += 1.5 ; graphText ("Parsed:",1,line) ; pdisp->nparsedBox = graphBoxStart () ; graphTextPtr (pdisp->nparsedText, 9, line, 64) ; graphBoxEnd () ; line += 1.5 ; graphText (" OK:",1,line) ; pdisp->nokBox = graphBoxStart () ; graphTextPtr (pdisp->nokText, 9, line, 64) ; graphBoxEnd () ; line += 1.5 ; graphText ("Errors:",1,line) ; pdisp->nerrorBox = graphBoxStart () ; graphTextPtr (pdisp->nerrorText, 9, line, 64) ; graphBoxEnd () ; showState (pdisp) ; graphRedraw () ; return; } /* parseDraw */ /***************************************/ static void parseDispDestroy (void) /* called when parseGraph dies */ /* parse-window function */ { ParseDisp pdisp = currentParseDisp("parseDispDestroy"); parseGraph = 0 ; if (pfExists(pdisp->pf)) pfDestroy (pdisp->pf); /* also closes file etc. */ pdisp->magic = 0; messfree (pdisp); /* The new data may have influenced the way the main-window * is drawn, so we simulate a change in writeAccess * to redraw - if the update has added longtext objects * (where there were no before) this is useful, to make * the [even in longtexts] button appear */ (writeAccessChangeRegister(0))(); return; } /* parseDispDestroy */ /********************************************/ static void showState (ParseDisp pdisp) /* parse-window function */ { PF pf ; if (!parseDispExists(pdisp)) return; pf = pdisp->pf ; /* shortcut to pf */ /* N.B. we always show the state even when we've finished parsing because */ /* otherwise the window goes blank at the end of parsing, infuriating for */ /* the user. */ if (!pfExists(pf)) strcpy (pdisp->itemText, ""); else strcpy (pdisp->itemText, messprintf ("%d %.40s", pf->nob, pf->obname ? pf->obname : "")) ; graphBoxDraw (pdisp->itemBox, BLACK, WHITE) ; if (!pfExists(pf)) strcpy (pdisp->lineText, ""); else { long int pos; Stack s = stackCreate (64); if (pf->state != DONE) { pushText (s, messprintf ("%d", aceInStreamLine (pf->parse_in)-1)); if (pf->file_length && aceInStreamPos (pf->parse_in, &pos)) { float percentage = (100.0 * pos) / pf->file_length; catText (s, messprintf (" (%2.0f%%)", percentage)); } } else { pushText (s, messprintf ("%d", pf->curr_line)) ; if (pf->has_length) { float percentage ; percentage = (100.0 * pf->file_pos) / pf->file_length; catText (s, messprintf (" (%2.0f%%)", percentage)); } } if (pf->currentLineText) { catText (s, " "); catText (s, pf->currentLineText); } strncpy (pdisp->lineText, stackText(s, 0), 64); pdisp->lineText[64-1] = '\0'; stackDestroy (s); } graphBoxDraw (pdisp->lineBox, BLACK, WHITE) ; /* Show some stats on parsing, currently we only show the totals, we could */ /* show everything if required... */ /* */ if (!pfExists(pf)) { strcpy(pdisp->nparsedText, "") ; strcpy(pdisp->nokText, "") ; strcpy(pdisp->nerrorText, "") ; } else { pf->nok = pf->nadded + pf->neditted + pf->ndeleted + pf->nrenamed + pf->naliased + pf->narray_added + pf->narray_empty + pf->narray_deleted ; pf->nerr = pf->ngen_err + pf->nerror + pf->narray_err ; strcpy(pdisp->nparsedText, messprintf("%d", pf->nob)) ; strcpy(pdisp->nokText, messprintf("%d", pf->nok)) ; strcpy(pdisp->nerrorText, messprintf("%d", pf->nerr)) ; } graphBoxDraw(pdisp->nparsedBox, BLACK, WHITE) ; graphBoxDraw(pdisp->nokBox, BLACK, WHITE) ; graphBoxDraw(pdisp->nerrorBox, BLACK, WHITE) ; graphProcessEvents (); return; } /* showState */ #endif /* !NON_GRAPHIC */ /************************************************************/ /************************** end of file *********************/ /************************************************************/