blob: aa68ce90d19f77a50cc12a9dc7789a8df1bd6613 [file] [log] [blame]
/*
* oper.c - Management for operations for "treecc".
*
* Copyright (C) 2001 Southern Storm Software, Pty Ltd.
*
* 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
*/
#include "system.h"
#include "input.h"
#include "info.h"
#include "errors.h"
#ifdef __cplusplus
extern "C" {
#endif
void TreeCCOperationFree(TreeCCOperation *oper)
{
TreeCCParam *param, *nextParam;
TreeCCOperationCase *operCase, *nextCase;
TreeCCTrigger *trigger, *nextTrigger;
/* Free the name and return type */
free(oper->name);
if(oper->className)
{
free(oper->className);
}
free(oper->returnType);
/* Free the parameters */
param = oper->params;
while(param != 0)
{
nextParam = param->next;
if(param->name)
{
free(param->name);
}
free(param->type);
param = nextParam;
}
/* Free the operation cases */
operCase = oper->firstCase;
while(operCase != 0)
{
nextCase = operCase->next;
if(operCase->code && !(operCase->nextHeader))
{
free(operCase->code);
}
trigger = operCase->triggers;
while(trigger != 0)
{
nextTrigger = trigger->next;
free(trigger);
trigger = nextTrigger;
}
free(operCase);
operCase = nextCase;
}
/* Free the array of sorted operation cases */
if(oper->sortedCases)
{
free(oper->sortedCases);
}
/* Free the operation block itself */
free(oper);
}
TreeCCOperation *TreeCCOperationCreate(TreeCCContext *context,
char *returnType, char *name,
char *className, char *defValue,
TreeCCParam *params, int flags,
int numTriggers, char *filename,
long linenum)
{
TreeCCOperation *oper;
unsigned int hash;
/* Print debugging information if required */
if(context->debugMode)
{
TreeCCParam *param;
TreeCCDebug(linenum, "%%operation %s %s%s%s %d",
returnType,
(className ? className : ""),
(className ? "::" : ""),
name, flags);
param = params;
while(param != 0)
{
TreeCCDebug(linenum, "%%param %s %s %d",
param->type, (param->name ? param->name : "no_name"),
param->flags);
param = param->next;
}
}
/* Allocate the operation block */
if((oper = (TreeCCOperation *)malloc(sizeof(TreeCCOperation))) == 0)
{
TreeCCOutOfMemory(context->input);
}
/* Initialise the operation block */
oper->name = name;
oper->className = className;
oper->returnType = returnType;
oper->defValue = defValue;
oper->params = params;
oper->flags = flags;
oper->numTriggers = numTriggers;
oper->filename = filename;
oper->linenum = linenum;
oper->firstCase = 0;
oper->lastCase = 0;
oper->sortedCases = 0;
oper->numCases = 0;
oper->header = context->headerStream;
oper->source = context->sourceStream;
/* Add the operation to the hash table */
hash = (TreeCCHashString(name) & (TREECC_HASH_SIZE - 1));
oper->nextHash = context->operHash[hash];
context->operHash[hash] = oper;
/* Done */
return oper;
}
TreeCCOperation *TreeCCOperationFind(TreeCCContext *context, char *name)
{
unsigned int hash = (TreeCCHashString(name) & (TREECC_HASH_SIZE - 1));
TreeCCOperation *oper = context->operHash[hash];
while(oper != 0)
{
if(!strcmp(oper->name, name))
{
return oper;
}
oper = oper->nextHash;
}
return 0;
}
TreeCCOperationCase *TreeCCOperationAddCase
(TreeCCContext *context, TreeCCOperation *oper,
TreeCCTrigger *triggers, char *filename, long linenum)
{
TreeCCOperationCase *operCase;
/* Print debugging information if required */
if(context->debugMode)
{
if(!triggers)
{
TreeCCDebug(linenum, "%%case %s", oper->name);
}
else
{
TreeCCDebug(linenum, "%%case %s %s", triggers->node->name,
oper->name);
}
}
/* Allocate the operation case block */
if((operCase = (TreeCCOperationCase *)malloc(sizeof(TreeCCOperationCase)))
== 0)
{
TreeCCOutOfMemory(context->input);
}
/* Initialise the operation case block */
operCase->triggers = triggers;
operCase->code = 0;
operCase->oper = oper;
operCase->number = 0;
operCase->filename = filename;
operCase->linenum = linenum;
operCase->codeFilename = 0;
operCase->codeLinenum = 0;
operCase->next = 0;
operCase->nextHeader = 0;
/* Add the case to the operation's case list */
if(oper->lastCase)
{
oper->lastCase->next = operCase;
}
else
{
oper->firstCase = operCase;
}
oper->lastCase = operCase;
++(oper->numCases);
/* Return the case to the caller */
return operCase;
}
/*
* Verify the coverage of an operation over a node hierarchy.
*/
static void VerifyHierarchy(TreeCCContext *context, TreeCCOperation *oper,
TreeCCNode *node)
{
if((node->flags & TREECC_NODE_MARK(0)) != 0)
{
return;
}
if((node->flags & TREECC_NODE_ABSTRACT) == 0)
{
TreeCCErrorOnLine(context->input, oper->filename, oper->linenum,
"node type `%s' is not handled in operation `%s'",
node->name, oper->name);
return;
}
node = node->firstChild;
while(node != 0)
{
VerifyHierarchy(context, oper, node);
node = node->nextSibling;
}
}
/*
* Append a parameter type name to a comma-separated string.
*/
static char *AppendParam(TreeCCContext *context, char *str, const char *type)
{
if(str)
{
int slen = strlen(str);
int tlen = strlen(type);
str = (char *)realloc(str, slen + tlen + 3);
if(!str)
{
TreeCCOutOfMemory(context->input);
}
strcpy(str + slen, ", ");
strcpy(str + slen + 2, type);
return str;
}
else
{
return TreeCCDupString((char *)type);
}
}
/*
* Forward declaration.
*/
static void MarkMultiCase(TreeCCOperationCase **sortedCases, int base,
int multiplier, TreeCCParam *nextParam,
TreeCCOperationCase *operCase,
TreeCCTrigger *nextTrigger);
/*
* Scan down a node type hierarchy to mark all covered children.
*/
static void MarkMultiScan(TreeCCOperationCase **sortedCases, int base,
int multiplier, TreeCCParam *nextParam,
TreeCCOperationCase *operCase,
TreeCCTrigger *nextTrigger, TreeCCNode *node)
{
/* Go in one level for this node */
MarkMultiCase(sortedCases, base + node->position * multiplier,
multiplier * nextParam->size, nextParam->next,
operCase, nextTrigger->next);
/* Scan all of the children */
node = node->firstChild;
while(node != 0)
{
MarkMultiScan(sortedCases, base, multiplier, nextParam,
operCase, nextTrigger, node);
node = node->nextSibling;
}
}
/*
* Mark all of the node types associated with an operation case.
*/
static void MarkMultiCase(TreeCCOperationCase **sortedCases, int base,
int multiplier, TreeCCParam *nextParam,
TreeCCOperationCase *operCase,
TreeCCTrigger *nextTrigger)
{
/* Scan for the next trigger parameter */
while(nextParam != 0 && (nextParam->flags & TREECC_PARAM_TRIGGER) == 0)
{
nextParam = nextParam->next;
}
/* If we are out of triggers, then we need to set the current value */
if(!nextParam)
{
if(sortedCases[base] == 0)
{
sortedCases[base] = operCase;
}
return;
}
/* Scan down the node type hierarchy for this trigger level */
MarkMultiScan(sortedCases, base, multiplier, nextParam,
operCase, nextTrigger, nextTrigger->node);
}
/*
* Forward declaration.
*/
static void VerifyMultiCoverage(TreeCCContext *context, TreeCCOperation *oper,
TreeCCOperationCase **sortedCases, int base,
int multiplier, TreeCCParam *nextParam,
TreeCCNode **nodeStack,
TreeCCNode **nodeStackTop);
/*
* Scan all children to verify a multi-coverage trigger.
*/
static void VerifyMultiScan(TreeCCContext *context, TreeCCOperation *oper,
TreeCCOperationCase **sortedCases, int base,
int multiplier, TreeCCParam *nextParam,
TreeCCNode *node, TreeCCNode **nodeStack,
TreeCCNode **nodeStackTop)
{
/* Go in one level for this node */
*nodeStackTop = node;
VerifyMultiCoverage(context, oper, sortedCases,
base + node->position * multiplier,
multiplier * nextParam->size,
nextParam->next, nodeStack, nodeStackTop + 1);
/* Scan all of the children */
node = node->firstChild;
while(node != 0)
{
VerifyMultiScan(context, oper, sortedCases, base, multiplier,
nextParam, node, nodeStack, nodeStackTop);
node = node->nextSibling;
}
}
/*
* Verify coverage of a multi-trigger operation.
*/
static void VerifyMultiCoverage(TreeCCContext *context, TreeCCOperation *oper,
TreeCCOperationCase **sortedCases, int base,
int multiplier, TreeCCParam *nextParam,
TreeCCNode **nodeStack,
TreeCCNode **nodeStackTop)
{
TreeCCNode *type;
/* Scan for the next trigger */
while(nextParam != 0 && (nextParam->flags & TREECC_PARAM_TRIGGER) == 0)
{
nextParam = nextParam->next;
}
/* If we are out of triggers, then we need to do a test */
if(!nextParam)
{
if(sortedCases[base] == 0)
{
/* No case: report an error if none of the node types
that led us here are abstract */
int posn;
int isAbstract = 0;
for(posn = 0; posn < oper->numTriggers; ++posn)
{
if((nodeStack[posn]->flags & TREECC_NODE_ABSTRACT) != 0)
{
isAbstract = 1;
break;
}
}
if(!isAbstract)
{
char *str = 0;
for(posn = 0; posn < oper->numTriggers; ++posn)
{
str = AppendParam(context, str, nodeStack[posn]->name);
}
if(!str)
{
str = TreeCCDupString("?");
}
TreeCCErrorOnLine(context->input, oper->filename, oper->linenum,
"case `%s' is missing from operation `%s'",
str, oper->name);
free(str);
}
}
return;
}
/* Scan down the node type hierarchy for this parameter level */
type = TreeCCNodeFindByType(context, nextParam->type);
if(!type)
{
return;
}
VerifyMultiScan(context, oper, sortedCases, base,
multiplier, nextParam, type,
nodeStack, nodeStackTop);
}
/*
* Test the coverage of a multi-trigger operation.
*/
static void MultiCoverageTest(TreeCCContext *context, TreeCCOperation *oper)
{
TreeCCParam *param;
TreeCCParam *param2;
TreeCCNode *type;
TreeCCNode *type2;
int size;
TreeCCNode **nodeStack;
TreeCCOperationCase *operCase;
/* Determine the size of the "sortedCases" array, and also
check to make sure there are no overlaps between triggers
that will cause position information to be inconsistent */
size = 1;
param = oper->params;
while(param != 0)
{
if((param->flags & TREECC_PARAM_TRIGGER) != 0)
{
type = TreeCCNodeFindByType(context, param->type);
if(type)
{
param->size = TreeCCNodeAssignPositions(type);
size *= param->size;
param2 = oper->params;
while(param2 != 0)
{
if((param2->flags & TREECC_PARAM_TRIGGER) != 0 &&
param2 != param)
{
type2 = TreeCCNodeFindByType(context, param2->type);
if(type2 && type2 != type)
{
if(TreeCCNodeInheritsFrom(type, type2) ||
TreeCCNodeInheritsFrom(type2, type))
{
TreeCCErrorOnLine(context->input,
oper->filename, oper->linenum,
"overlap between trigger types");
return;
}
}
}
param2 = param2->next;
}
}
}
param = param->next;
}
/* Create the "sortedCases" array */
if((oper->sortedCases = (TreeCCOperationCase **)calloc
(size, sizeof(TreeCCOperationCase *))) == 0)
{
TreeCCOutOfMemory(context->input);
}
/* Find the operation case to use for each combination of values */
operCase = oper->firstCase;
while(operCase != 0)
{
MarkMultiCase(oper->sortedCases, 0, 1, oper->params,
operCase, operCase->triggers);
operCase = operCase->next;
}
/* Verify that every combination of values has been handled */
nodeStack = (TreeCCNode **)calloc(oper->numTriggers, sizeof(TreeCCNode *));
if(!nodeStack)
{
TreeCCOutOfMemory(context->input);
}
VerifyMultiCoverage(context, oper, oper->sortedCases, 0, 1,
oper->params, nodeStack, nodeStack);
free(nodeStack);
}
/*
* Test that an operation covers all relevant node type cases.
*/
static void OperationTest(TreeCCContext *context, TreeCCOperation *oper)
{
TreeCCOperationCase *operCase;
TreeCCTrigger *trigger;
TreeCCParam *param;
TreeCCNode *node;
/* Clear the marking bits */
TreeCCNodeClearMarking(context, TREECC_NODE_MARK_BITS);
/* Print an error if the operation has no cases at all */
if(!(oper->firstCase))
{
TreeCCErrorOnLine(context->input, oper->filename, oper->linenum,
"operation `%s' has no cases", oper->name);
}
/* Single or multiple triggers? */
if(oper->numTriggers == 1)
{
/* Mark the coverage of all operation cases */
operCase = oper->firstCase;
while(operCase != 0)
{
trigger = operCase->triggers;
if(trigger != 0)
{
if((trigger->node->flags & TREECC_NODE_MARK(0)) != 0)
{
TreeCCErrorOnLine(context->input, operCase->filename,
operCase->linenum,
"node type `%s' is handled multiple "
"times for operation `%s'",
trigger->node->name, oper->name);
}
else
{
trigger->node->flags |= TREECC_NODE_MARK(0);
}
}
operCase = operCase->next;
}
/* Walk the node hierarchy to verify coverage */
param = oper->params;
while(param != 0)
{
if((param->flags & TREECC_PARAM_TRIGGER) != 0)
{
node = TreeCCNodeFindByType(context, param->type);
if(node)
{
VerifyHierarchy(context, oper, node);
}
}
param = param->next;
}
}
else if(oper->numTriggers == 0)
{
/* Zero triggers: look for a single case definition */
operCase = oper->firstCase;
if(operCase && operCase->next)
{
do
{
operCase = operCase->next;
TreeCCErrorOnLine(context->input, operCase->filename,
operCase->linenum,
"multiple definitions for operation `%s'",
oper->name);
}
while(operCase->next != 0);
TreeCCErrorOnLine(context->input, oper->firstCase->filename,
oper->firstCase->linenum,
"first definition here");
}
}
else
{
/* Multiple triggers need a more complex algorithm to verify */
if(oper->firstCase != 0)
{
MultiCoverageTest(context, oper);
}
}
}
/*
* Compare two operation cases for a sort operation.
*/
static int CaseCompare(const void *e1, const void *e2)
{
TreeCCOperationCase *operCase1 = *((TreeCCOperationCase **)e1);
TreeCCOperationCase *operCase2 = *((TreeCCOperationCase **)e2);
TreeCCTrigger *trigger1 = operCase1->triggers;
TreeCCTrigger *trigger2 = operCase2->triggers;
TreeCCNode *ancestor1;
TreeCCNode *ancestor2;
while(trigger1 != 0 && trigger2 != 0)
{
if(trigger1->node != trigger2->node)
{
/* See if trigger1->node inherits from trigger2->node */
ancestor1 = trigger1->node;
while(ancestor1->parent != 0)
{
if(ancestor1->parent == trigger2->node)
{
return -1;
}
ancestor1 = ancestor1->parent;
}
/* See if trigger2->node inherits from trigger1->node */
ancestor2 = trigger2->node;
while(ancestor2->parent != 0)
{
if(ancestor2->parent == trigger1->node)
{
return 1;
}
ancestor2 = ancestor2->parent;
}
/* If the ancestors are separate, then use the node numbers
to order the separate trees. Note: this is highly unlikely
because operation definition forces all cases to inherit
from a declared common type. However, if we change this
requirement in the future, this code will be needed, so
we are being paranoid and including it now */
if(ancestor1 != ancestor2)
{
if(ancestor1->number < ancestor2->number)
{
return -1;
}
else
{
return 1;
}
}
/* This is the hard case: the two nodes are in separate
subtrees of the same ancestor, but we still need an
ordering between them. We need to find the lowest
common ancestor, and the nodes just below it */
ancestor1 = trigger1->node;
while(ancestor1->parent != 0)
{
ancestor1 = ancestor1->parent;
ancestor1->flags |= TREECC_NODE_MARK(0);
}
ancestor2 = trigger2->node;
while(ancestor2->parent != 0)
{
if((ancestor2->parent->flags & TREECC_NODE_MARK(0)) != 0)
{
break;
}
ancestor2 = ancestor2->parent;
}
ancestor1 = trigger1->node;
while(ancestor1->parent != 0)
{
ancestor1 = ancestor1->parent;
ancestor1->flags &= ~(TREECC_NODE_MARK(0));
}
ancestor1 = trigger1->node;
while(ancestor1->parent != ancestor2->parent)
{
ancestor1 = ancestor1->parent;
}
/* ancestor1 and ancestor2 are now one step down
from the common ancestor that we found. Use
their node numbers to order the trees */
if(ancestor1->number < ancestor2->number)
{
return -1;
}
else
{
return 1;
}
}
trigger1 = trigger1->next;
trigger2 = trigger2->next;
}
return 0;
}
/*
* Sort a set of operation cases so that A precedes B
* if A inherits directly or indirectly from B. Operations
* with multiple triggers sort on the first, then the second,
* then the third, etc.
*/
static void SortCases(TreeCCContext *context, TreeCCOperation *oper)
{
TreeCCOperationCase **caseList;
TreeCCOperationCase *operCase;
int num;
/* Bail out early if there are 0 or 1 cases, or no triggers */
if(oper->numCases < 2 || !(oper->numTriggers))
{
return;
}
/* Clear the marking bits */
TreeCCNodeClearMarking(context, TREECC_NODE_MARK_BITS);
/* Build an array that contains all operation cases */
if((caseList = (TreeCCOperationCase **)malloc
(sizeof(TreeCCOperationCase *) * oper->numCases)) == 0)
{
TreeCCOutOfMemory(context->input);
}
num = 0;
operCase = oper->firstCase;
while(operCase != 0)
{
caseList[num++] = operCase;
operCase = operCase->next;
}
/* Sort the case list. There are probably more efficient
ways to do this (e.g. topological sorting), but this
version is perhaps easier to understand and maintain */
#if HAVE_QSORT
qsort(caseList, oper->numCases, sizeof(TreeCCOperationCase *), CaseCompare);
#else
{
int i, j;
num = oper->numCases;
for(i = 0; i < (num - 1); ++i)
{
for(j = (i + 1); j < num; ++j)
{
if(CaseCompare(&(caseList[i]), &(caseList[j])) > 0)
{
operCase = caseList[i];
caseList[i] = caseList[j];
caseList[j] = operCase;
}
}
}
}
#endif
/* Build a new case list from the sorted array */
oper->firstCase = caseList[0];
for(num = 1; num < oper->numCases; ++num)
{
caseList[num - 1]->next = caseList[num];
}
oper->lastCase = caseList[oper->numCases - 1];
caseList[oper->numCases - 1]->next = 0;
free(caseList);
}
void TreeCCOperationValidate(TreeCCContext *context)
{
unsigned int hash;
TreeCCOperation *oper;
for(hash = 0; hash < TREECC_HASH_SIZE; ++hash)
{
oper = context->operHash[hash];
while(oper != 0)
{
if((oper->flags & TREECC_OPER_VIRTUAL) == 0)
{
/* Non-virtuals must be sorted for code generation,
and we also rely upon the sorted order to help us
test for operation coverage when multiple triggers
are used by the operation */
SortCases(context, oper);
}
OperationTest(context, oper);
oper = oper->nextHash;
}
}
}
TreeCCOperationCase *TreeCCOperationFindCase
(TreeCCContext *context, TreeCCNode *node, char *name)
{
TreeCCOperation *oper;
TreeCCOperationCase *operCase;
oper = TreeCCOperationFind(context, name);
if(!oper)
{
return 0;
}
operCase = oper->firstCase;
while(operCase != 0)
{
if(operCase->triggers && operCase->triggers->node == node)
{
return operCase;
}
operCase = operCase->next;
}
return 0;
}
void TreeCCOperationVisitAll(TreeCCContext *context,
TreeCCOperationVisitor visitor)
{
unsigned int hash;
TreeCCOperation *oper;
for(hash = 0; hash < TREECC_HASH_SIZE; ++hash)
{
oper = context->operHash[hash];
while(oper != 0)
{
(*visitor)(context, oper);
oper = oper->nextHash;
}
}
}
#ifdef __cplusplus
};
#endif