/**************************************************************/ /* ********************************************************** */ /* * * */ /* * TABLEAU PROOF TREES * */ /* * * */ /* * Copyright (C) 1998, 1999, 2000, 2001 * */ /* * MPI fuer Informatik * */ /* * * */ /* * This program 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 not, write * */ /* * to the Free Software Foundation, Inc., 59 Temple * */ /* * Place, Suite 330, Boston, MA 02111-1307 USA * */ /* * * */ /* $Revision: 21527 $ * */ /* $State$ * */ /* $Date: 2005-04-24 21:10:28 -0700 (Sun, 24 Apr 2005) $ * */ /* $Author: duraid $ * */ /* * * */ /* * * */ /* * Contact: * */ /* * Christoph Weidenbach * */ /* * MPI fuer Informatik * */ /* * Stuhlsatzenhausweg 85 * */ /* * 66123 Saarbruecken * */ /* * Email: weidenb@mpi-sb.mpg.de * */ /* * Germany * */ /* * * */ /* ********************************************************** */ /* $RCSfile$ */ #include "tableau.h" /* for graph output */ extern BOOL pcheck_ClauseCg; extern GRAPHFORMAT pcheck_GraphFormat; TABPATH tab_PathCreate(int MaxLevel, TABLEAU Tab) /************************************************************** INPUT: A tableau, its maximum expected depth RETURNS: A path consisting of the root node of the tableau which can have a max length of ***************************************************************/ { TABPATH TabPath; TabPath = (TABPATH)memory_Malloc(sizeof(TABPATH_NODE)); TabPath->Path = (TABLEAU*)memory_Malloc(sizeof(TABLEAU)*(MaxLevel+1)); TabPath->Path[0] = Tab; TabPath->MaxLength = MaxLevel; TabPath->Length = 0; return TabPath; } void tab_PathDelete(TABPATH TabPath) /************************************************************** INPUT: A tableau path. RETURNS: Nothing. EFFECTS: The path is deleted. ***************************************************************/ { memory_Free(TabPath->Path, (TabPath->MaxLength+1)*sizeof(TABLEAU)); memory_Free(TabPath, sizeof(TABPATH_NODE)); } BOOL tab_PathContainsClause(TABPATH Path, CLAUSE Clause) /************************************************************** INPUT: A tableau path, a clause RETURNS: TRUE iff the clause exists on its level (wrt. the path) in the tableau ***************************************************************/ { LIST Scan; if (clause_SplitLevel(Clause) > tab_PathLength(Path)) return FALSE; for (Scan = tab_Clauses(tab_PathNthNode(Path, clause_SplitLevel(Clause))); !list_Empty(Scan); Scan = list_Cdr(Scan)) { if (list_Car(Scan) == Clause) return TRUE; } return FALSE; } static BOOL tab_PathContainsClauseSoft(TABPATH Path, CLAUSE Clause) /************************************************************** INPUT: A tableau path, a clause RETURNS: TRUE iff the clause is on one of the levels in the tableau traversed by the path. Different from tab_PathContainsClauseSoft, since it does not expect the clause on its split level. ***************************************************************/ { LIST Scan; int Level; if (clause_SplitLevel(Clause) > tab_PathLength(Path)) return FALSE; for (Level = 0; Level <= tab_PathLength(Path); Level++) { for (Scan = tab_Clauses(tab_PathNthNode(Path, Level)); !list_Empty(Scan); Scan = list_Cdr(Scan)) { if (list_Car(Scan) == Clause) return TRUE; } } return FALSE; } /* unused */ /*static*/ BOOL tab_PathContainsClauseRobust(TABPATH P, CLAUSE C) /************************************************************** INPUT: A tableau path, a clause RETURNS: TRUE if clause can be find on the path (not necessarily on its level) EFFECTS: Prints a note if clause cannot be found on its level. Intended for debugging. ***************************************************************/ { if (tab_PathContainsClause(P,C)) return TRUE; if (tab_PathContainsClauseSoft(P,C)) { fputs("NOTE: Clause is found on path, but not indexed by level.\n", stderr); clause_PParentsFPrint(stderr,C); fflush(stderr); return TRUE; } return FALSE; } void tab_AddSplitAtCursor(TABPATH Path, BOOL LeftSide) /************************************************************** INPUT: A tableau path, a flag RETURNS: Nothing. EFFECTS: Extends the tableau containing the path to the left if is TRUE, to the right otherwise ***************************************************************/ { TABLEAU Tab, NewBranch; Tab = tab_PathTop(Path); NewBranch = tab_CreateNode(); if (LeftSide) { #ifdef CHECK if (!tab_LeftBranchIsEmpty(Tab)) { misc_StartErrorReport(); misc_ErrorReport("\n In tab_AddSplitAtCursor: Recreating existing"); misc_ErrorReport(" left branch in tableau.\n"); misc_FinishErrorReport(); } #endif tab_SetLeftBranch(Tab,NewBranch); } else { #ifdef CHECK if (!tab_RightBranchIsEmpty(Tab)) { misc_StartErrorReport(); misc_ErrorReport("\n In tab_AddSplitAtCursor: Recreating existing"); misc_ErrorReport(" right branch in tableau.\n"); misc_FinishErrorReport(); } #endif tab_SetRightBranch(Tab, NewBranch); } tab_PathPush(NewBranch, Path); } void tab_AddClauseOnItsLevel(CLAUSE C, TABPATH Path) /************************************************************** INPUT: A clause, a tableau path RETURNS: Nothing EFFECTS: Adds the clause on its split level which must belong to ***************************************************************/ { int Level; Level = clause_SplitLevel(C); if (Level > tab_PathLength(Path)) { misc_StartUserErrorReport(); misc_UserErrorReport("\nError: Split level of some clause "); misc_UserErrorReport("\nis higher than existing level."); misc_UserErrorReport("\nThis may be a bug in the proof file."); misc_FinishUserErrorReport(); } tab_AddClause(C, tab_PathNthNode(Path, clause_SplitLevel(C))); } int tab_Depth(TABLEAU T) /************************************************************** INPUT: A tableau RETURNS: The depth of the tableau ***************************************************************/ { if (tab_IsEmpty(T)) return 0; if (tab_IsLeaf(T)) return 0; else return (misc_Max(tab_Depth(tab_RightBranch(T))+1, tab_Depth(tab_LeftBranch(T)))+1); } static BOOL tab_HasEmptyClause(TABLEAU T) /************************************************************** INPUT: A tableau RETURNS: TRUE iff an empty clause is among the clauses on this level ***************************************************************/ { LIST Scan; for (Scan = tab_Clauses(T); !list_Empty(Scan); Scan = list_Cdr(Scan)) if (clause_IsEmptyClause(list_Car(Scan))) return TRUE; return FALSE; } BOOL tab_IsClosed(TABLEAU T) /************************************************************** INPUT: A Tableau RETURNS: TRUE iff the tableau is closed. (NOTE: FALSE if the tableau is empty) ***************************************************************/ { if (tab_IsEmpty(T)) return FALSE; if (tab_HasEmptyClause(T)) return TRUE; /* * now tableau can only be closed * if there has been a split, and * both subtableaus are closed */ if (tab_RightBranchIsEmpty(T) || tab_LeftBranchIsEmpty(T)) { printf("\nopen node label: %d", T->Label); fflush(stdout); return FALSE; } return tab_IsClosed(tab_RightBranch(T)) && tab_IsClosed(tab_LeftBranch(T)); } static LIST tab_DeleteFlat(TABLEAU T) /************************************************************** INPUT: A tableau RETURNS: The list of its clauses on the first level of the tableau EFFECTS: Frees the root tableau node. ***************************************************************/ { LIST Clauses; Clauses = tab_Clauses(T); memory_Free(T, sizeof(TABLEAU_NODE)); return Clauses; } static void tab_DeleteGen(TABLEAU T, LIST* Clauses, BOOL DeleteClauses) /************************************************************** INPUT: A tableau, a list of clauses by reference, a flag RETURNS: Nothing EFFECTS: Depending on , all clauses in the tableau are added to or just deleted. The memory for the tableau and its clause lists is freed. ***************************************************************/ { if (tab_IsEmpty(T)) return; tab_DeleteGen(tab_RightBranch(T), Clauses, DeleteClauses); tab_DeleteGen(tab_LeftBranch(T), Clauses, DeleteClauses); list_Delete(tab_RightSplitClauses(T)); if (DeleteClauses) list_Delete(tab_Clauses(T)); else *Clauses = list_Nconc(tab_Clauses(T), *Clauses); tab_DeleteFlat(T); } static void tab_DeleteCollectClauses(TABLEAU T, LIST* Clauses) /************************************************************** INPUT: A tableau, a list of clauses by reference RETURNS: Nothing EFFECTS: Frees the memory of the tableau, but collects its clauses ***************************************************************/ { tab_DeleteGen(T, Clauses, FALSE); } void tab_Delete(TABLEAU T) /************************************************************** INPUT: A tableau RETURNS: Nothing EFFECTS: Frees the memory of the tableau ***************************************************************/ { LIST Redundant; Redundant = list_Nil(); tab_DeleteGen(T, &Redundant, TRUE); } static void tab_SetSplitLevelsRec(TABLEAU T, int Level) /************************************************************** INPUT: A tableau RETURNS: Nothing EFFECTS: The split levels of the clauses in the tableau are set to the level of the tableau level they are contained in. ***************************************************************/ { LIST Scan; if (tab_IsEmpty(T)) return; for (Scan = tab_Clauses(T); !list_Empty(Scan); Scan = list_Cdr(Scan)) { clause_SetSplitLevel(list_Car(Scan), Level); if (Level >0) { clause_ClearSplitField(list_Car(Scan)); clause_SetSplitFieldBit(list_Car(Scan), Level); } else clause_SetSplitField(list_Car(Scan), (SPLITFIELD)NULL,0); } tab_SetSplitLevelsRec(tab_RightBranch(T), Level+1); tab_SetSplitLevelsRec(tab_LeftBranch(T), Level+1); } void tab_SetSplitLevels(TABLEAU T) /************************************************************** INPUT: A tableau RETURNS: Nothing EFFECTS: The split levels of the clauses in the tableau are set to the level of the tableau they belong to. ***************************************************************/ { tab_SetSplitLevelsRec(T,0); } TABLEAU tab_PruneClosedBranches(TABLEAU T, LIST* Clauses) /************************************************************** INPUT: A tableau, a list of clauses by reference. RETURNS: The (destructively) reduced tableau: Descendants of nodes that have an empty clause are deleted. EFFECTS: The tableau is modified. ***************************************************************/ { if (tab_IsEmpty(T)) return T; /* if there is an empty clause on this level, delete subtrees */ if (tab_HasEmptyClause(T)) { tab_DeleteCollectClauses(tab_RightBranch(T), Clauses); tab_DeleteCollectClauses(tab_LeftBranch(T), Clauses); tab_SetRightBranch(T, tab_EmptyTableau()); tab_SetLeftBranch(T, tab_EmptyTableau()); list_Delete(tab_RightSplitClauses(T)); tab_SetRightSplitClauses(T, list_Nil()); tab_SetSplitClause(T,clause_Null()); tab_SetLeftSplitClause(T, clause_Null()); } /* else recursively prune subtrees */ else { tab_SetRightBranch(T, tab_PruneClosedBranches(tab_RightBranch(T), Clauses)); tab_SetLeftBranch(T, tab_PruneClosedBranches(tab_LeftBranch(T), Clauses)); } return T; } TABLEAU tab_RemoveIncompleteSplits(TABLEAU T, LIST* Clauses) /************************************************************** INPUT: A Tableau, a list of clauses by reference RETURNS: The reduced tableau: If a node has exactly one successor (that is, the corresponding split was not completed), delete the successor and move its subtrees to . EFFECTS: The successor node is deleted, and its clauses added to ***************************************************************/ { LIST NewClauses; TABLEAU Child; if (tab_IsEmpty(T)) return T; if (tab_IsLeaf(T)) return T; if (!tab_IsEmpty(tab_RightBranch(T)) && !tab_IsEmpty(tab_LeftBranch(T))) { tab_SetRightBranch(T, tab_RemoveIncompleteSplits(tab_RightBranch(T), Clauses)); tab_SetLeftBranch(T, tab_RemoveIncompleteSplits(tab_LeftBranch(T), Clauses)); return T; } if (tab_IsEmpty(tab_RightBranch(T))) Child = tab_LeftBranch(T); else Child = tab_RightBranch(T); Child = tab_RemoveIncompleteSplits(Child, Clauses); tab_SetLeftBranch(T, tab_LeftBranch(Child)); tab_SetRightBranch(T, tab_RightBranch(Child)); /* copy split data */ tab_SetSplitClause(T, tab_SplitClause(Child)); tab_SetLeftSplitClause(T, tab_LeftSplitClause(Child)); tab_SetRightSplitClauses(T, tab_RightSplitClauses(Child)); /* delete ancestors of deleted clauses and remember */ NewClauses = tab_DeleteFlat(Child); (*Clauses) = list_Nconc(NewClauses, *Clauses); return T; } void tab_CheckEmpties(TABLEAU T) /************************************************************** INPUT: A tableau RETURNS: Nothing. EFFECTS: Prints warnings if non-leaf nodes contain empty clauses (which should not be the case after pruning any more), of if leaf nodes contain more than one empty clause ***************************************************************/ { LIST Scan, Empties; BOOL Printem; if (tab_IsEmpty(T)) return; /* get all empty clauses in this node */ Empties = list_Nil(); for (Scan = tab_Clauses(T); !list_Empty(Scan); Scan = list_Cdr(Scan)) { if (clause_IsEmptyClause(list_Car(Scan))) Empties = list_Cons(list_Car(Scan), Empties); } Printem = FALSE; if (!list_Empty(Empties) && !tab_IsLeaf(T)) { puts("\nNOTE: non-leaf node contains empty clauses."); Printem = TRUE; } if (tab_IsLeaf(T) && list_Length(Empties) > 1) { puts("\nNOTE: Leaf contains more than one empty clauses."); Printem = TRUE; } if (Printem) { puts("Clauses:"); clause_PParentsListPrint(tab_Clauses(T)); } list_Delete(Empties); tab_CheckEmpties(tab_LeftBranch(T)); tab_CheckEmpties(tab_RightBranch(T)); } void tab_GetAllEmptyClauses(TABLEAU T, LIST* L) /************************************************************** INPUT: A tableau, a list by reference RETURNS: All empty clauses in the tableau prepended to . ***************************************************************/ { if (tab_IsEmpty(T)) return; tab_GetAllEmptyClauses(tab_LeftBranch(T), L); tab_GetAllEmptyClauses(tab_RightBranch(T), L); } void tab_GetEarliestEmptyClauses(TABLEAU T, LIST* L) /************************************************************** INPUT : A tableau, a list of clauses by reference RETURNS: Nothing. EFFECTS: For each leaf node, adds empty clauses in leaf nodes to . If the leaf node contains only one empty clause, it is added to anyway. If the leaf node contains more than one empty clause, the earliest derived empty clause is added to . ***************************************************************/ { CLAUSE FirstEmpty; LIST Scan; if (tab_IsEmpty(T)) return; if (tab_IsLeaf(T)) { FirstEmpty = clause_Null(); for (Scan = tab_Clauses(T); !list_Empty(Scan); Scan = list_Cdr(Scan)) { if (clause_IsEmptyClause(list_Car(Scan))) { if (FirstEmpty == clause_Null()) FirstEmpty = list_Car(Scan); else if (clause_Number(FirstEmpty) > clause_Number(list_Car(Scan))) FirstEmpty = list_Car(Scan); } } if (FirstEmpty != clause_Null()) (*L) = list_Cons(FirstEmpty, *L); } tab_GetEarliestEmptyClauses(tab_LeftBranch(T), L); tab_GetEarliestEmptyClauses(tab_RightBranch(T), L); } void tab_ToClauseList(TABLEAU T, LIST* Proof) /************************************************************** INPUT: A tableau , a list of clauses RETURNS: Nothing. EFFECTS: All clauses in T are added to ***************************************************************/ { if (tab_IsEmpty(T)) return; (*Proof) = list_Nconc(list_Copy(tab_Clauses(T)), *Proof); tab_ToClauseList(tab_LeftBranch(T),Proof); tab_ToClauseList(tab_RightBranch(T),Proof); } static void tab_ToSeqProofOrdered(TABLEAU T, LIST* Proof) /************************************************************** INPUT: A tableau , a list of clauses representing a proof by reference RETURNS: The sequential proof corresponding to the tableau. ***************************************************************/ { LIST Scan; BOOL RightSplitRead, LeftSplitRead; if (tab_IsEmpty(T)) return; Scan = tab_Clauses(T); RightSplitRead = LeftSplitRead = FALSE; while (!list_Empty(Scan)) { /* insert left and right splits and descendants controlled by clause number */ if (!RightSplitRead && !tab_RightBranchIsEmpty(T) && clause_Number(list_Car(Scan)) < clause_Number(list_Car(tab_RightSplitClauses(T)))) { tab_ToSeqProofOrdered(tab_RightBranch(T), Proof); RightSplitRead = TRUE; } if (!LeftSplitRead && !tab_LeftBranchIsEmpty(T) && clause_Number(list_Car(Scan)) < clause_Number(tab_LeftSplitClause(T))) { tab_ToSeqProofOrdered(tab_LeftBranch(T), Proof); LeftSplitRead = TRUE; } (*Proof) = list_Cons(list_Car(Scan), *Proof); Scan = list_Cdr(Scan); } /* if a split clause with descendants has not been inserted yet, it been generated after all other clauses */ if (!RightSplitRead) tab_ToSeqProofOrdered(tab_RightBranch(T), Proof); if (!LeftSplitRead) tab_ToSeqProofOrdered(tab_LeftBranch(T), Proof); } /****************************************************************/ /* SPECIALS FOR GRAPHS */ /****************************************************************/ static void tab_LabelNodesRec(TABLEAU T, int* Num) /************************************************************** INPUT: A Tableau, a number by reference RETURNS: Nothing. EFFECTS: Labels the tableau nodes dflr, starting with ***************************************************************/ { if (tab_IsEmpty(T)) return; T->Label = *Num; (*Num)++; tab_LabelNodesRec(tab_LeftBranch(T), Num); tab_LabelNodesRec(tab_RightBranch(T), Num); } void tab_LabelNodes(TABLEAU T) /************************************************************** INPUT: A Tableau, a number by reference RETURNS: Nothing. EFFECTS: Labels the tableau nodes dflr, starting with 0 ***************************************************************/ { int Num; Num = 0; tab_LabelNodesRec(T, &Num); } static void tab_FPrintNodeLabel(FILE *File, TABLEAU T) /************************************************************** INPUT: A file handle, a tableau RETURNS: Nothing. EFFECTS: Prints the root node information to : clauses, split information ***************************************************************/ { LIST Scan; /* start printing of node label string */ fprintf(File, "\"label: %d\\n", T->Label); /* print left and right parts of split */ fputs("SplitClause : ", File); clause_PParentsFPrint(File, tab_SplitClause(T)); fputs("\\nLeft Clause : ", File); clause_PParentsFPrint(File, tab_LeftSplitClause(T)); fputs("\\nRightClauses: ", File); if (list_Empty(tab_RightSplitClauses(T))) fputs("[]\\n", File); else { clause_PParentsFPrint(File, list_Car(tab_RightSplitClauses(T))); fputs("\\n", File); for (Scan = list_Cdr(tab_RightSplitClauses(T)); !list_Empty(Scan); Scan = list_Cdr(Scan)) { fputs(" ", File); clause_PParentsFPrint(File, list_Car(Scan)); fputs("\\n", File); } } /* print clause at this level */ if (pcheck_ClauseCg) { if (list_Empty(tab_Clauses(T))) fputs("[]", File); else { for (Scan = tab_Clauses(T); !list_Empty(Scan); Scan = list_Cdr(Scan)){ clause_PParentsFPrint(File, list_Car(Scan)); fputs("\\n", File); } } } putc('"', File); /* close string */ } static void tab_FPrintEdgeCgFormat(FILE* File, int Source, int Target, BOOL Left) /************************************************************** INPUT: A file handle, two node labels, a flag RETURNS: Nothing. EFFECTS: Prints the edge denoted by and to , with an edge label (0/1) according to . The edge label is added since xvcg cannot handle ordered edges. ***************************************************************/ { fputs("\nedge: {", File); fprintf(File, "\nsourcename: \"%d\"", Source); fprintf(File, "\ntargetname: \"%d\"\n", Target); fputs("\nlabel: \"", File); if (Left) putc('0', File); else putc('1', File); fputs("\" }\n", File); } static void tab_FPrintEdgesCgFormat(FILE* File, TABLEAU T) /************************************************************** INPUT: A file handle, a tableau RETURNS: Nothing. EFFECTS: Prints edge information of in xvcg graph format to . ***************************************************************/ { if (tab_IsEmpty(T)) return; if (!tab_LeftBranchIsEmpty(T)) tab_FPrintEdgeCgFormat(File, T->Label, tab_LeftBranch(T)->Label, TRUE); if (!tab_RightBranchIsEmpty(T)) tab_FPrintEdgeCgFormat(File, T->Label, tab_RightBranch(T)->Label, FALSE); tab_FPrintEdgesCgFormat(File, tab_LeftBranch(T)); tab_FPrintEdgesCgFormat(File, tab_RightBranch(T)); } static void tab_FPrintNodesCgFormat(FILE* File, TABLEAU T) /************************************************************** INPUT: A file handle, a tableau RETURNS: Nothing EFFECTS: Prints egde information of in xvcg graph format to . ***************************************************************/ { if (tab_IsEmpty(T)) return; fputs("\nnode: {\n\nlabel: ", File); tab_FPrintNodeLabel(File, T); putc('\n', File); /* end label section */ fprintf(File, "title: \"%d\"\n", T->Label); fputs(" }\n", File); /* recursion */ tab_FPrintNodesCgFormat(File, tab_LeftBranch(T)); tab_FPrintNodesCgFormat(File, tab_RightBranch(T)); } static void tab_FPrintCgFormat(FILE* File, TABLEAU T) /************************************************************** INPUT: A file handle, a tableau RETURNS: Nothing. EFFECTS: Prints tableau as a graph in xvcg format to ***************************************************************/ { fputs("graph: \n{\ndisplay_edge_labels: yes\n", File); tab_FPrintNodesCgFormat(File, T); tab_FPrintEdgesCgFormat(File, T); fputs("}\n", File); } /* unused */ /*static*/ void tab_PrintCgFormat(TABLEAU T) /************************************************************** INPUT: A tableau. RETURNS: Nothing. EFFECTS: Print tableau in xvcg format to stdout ***************************************************************/ { tab_FPrintCgFormat(stdout, T); } /**************************************************************/ /* procedures for printing graph in da Vinci format */ /**************************************************************/ static void tab_FPrintDaVinciEdge(FILE* File, int L1, int L2) /************************************************************** INPUT: A file handle, two numbers RETURNS: Nothing EFFECTS: Print an edge in daVinci format ***************************************************************/ { fprintf(File, "l(\"%d->%d\"," ,L1,L2); fputs("e(\"\",[],\n", File); /* print child node as reference */ fprintf(File, "r(\"%d\")))\n", L2); } static void tab_FPrintDaVinciFormatRec(FILE* File, TABLEAU T) /************************************************************** INPUT: A file handle, a tableau RETURNS: Nothing EFFECTS: Prints tableau to in daVinci format ***************************************************************/ { /* print node label */ fprintf(File, "l(\"%d\",", T->Label); /* print node attributes */ fputs("n(\"\", [a(\"OBJECT\",", File); tab_FPrintNodeLabel(File, T); fputs(")],\n", File); /* print egde list */ putc('[', File); if (!tab_LeftBranchIsEmpty(T)) tab_FPrintDaVinciEdge(File, T->Label, tab_LeftBranch(T)->Label); if (!tab_RightBranchIsEmpty(T)) { if (!tab_LeftBranchIsEmpty(T)) putc(',', File); tab_FPrintDaVinciEdge(File, T->Label, tab_RightBranch(T)->Label); } fputs("]))", File); /* this ends the node description */ if (!tab_LeftBranchIsEmpty(T)) { putc(',', File); tab_FPrintDaVinciFormatRec(File, tab_LeftBranch(T)); } if (!tab_RightBranchIsEmpty(T)) { putc(',', File); tab_FPrintDaVinciFormatRec(File, tab_RightBranch(T)); } } static void tab_FPrintDaVinciFormat(FILE* File, TABLEAU T) /************************************************************** INPUT: A file handle , a tableau RETURNS: Nothing EFFECTS: Print tableau in daVinci format to ***************************************************************/ { fputs("[\n", File); tab_FPrintDaVinciFormatRec(File,T); fputs("]\n", File); } void tab_WriteTableau(TABLEAU T, const char* Filename, GRAPHFORMAT Format) /************************************************************** INPUT: A tableau, a filename RETURNS: Nothing. ***************************************************************/ { FILE* File; if (Format != DAVINCI && Format != XVCG) { misc_StartUserErrorReport(); misc_UserErrorReport("\nError: unknown output format for tableau.\n"); misc_FinishUserErrorReport(); } File = misc_OpenFile(Filename, "w"); if (Format == DAVINCI) tab_FPrintDaVinciFormat(File, T); else if (Format == XVCG) tab_FPrintCgFormat(File, T); misc_CloseFile(File, Filename); }