/* yacc.y parser for VRML97. C++ code!!! */
%{
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "vrml.H"
#include "parser.H"
#include "nodeCatalog.H"
#include "Proto.H"
#include "Stack.H"
#include "Assoc.H"
#include "error.H"

extern "C" {
  extern int yylex(void);
  extern FILE *yyin;
  extern void UnputToken(void), UnputString(char *string); /* in lex.l */
}

int yyparse(void);

/* name of file and number of line being parsed. */
char *yyfilename = (char *)0;
int lineno;

/* for reporting parse errors */
void yyerror(char *s)
{
  Fatal(1, NULL, "%s", s);
}

/* VRML parsing context */
class context {
public:
  SFNode *master;		/* contains master interface of the PROTO 
				 * for which the context is created */

  MFNode *sceneGraph;		/* root nodes within the context */
  assoc<comparable_string, SFNode *> namedNodes; /* DEF'ed nodes within the context */
  array<Proto *> protos;	/* PROTO and EXTERNPROTO nodes within the context */
  stack<SFNode *> nodeStack;	/* ancestors of current node being parsed */
  SFNode *curnode;		/* current node being parsed */

  context()
    {
      sceneGraph = new MFNode;
      namedNodes.default_value=0; namedNodes.default_key=0; 
      curnode = (SFNode *)0;
      master = (SFNode *)0;
    }

  Proto *lookup_proto(char *typeId)
    {
      for (int i=0; i<protos.size; i++)
	if (strcmp(protos[i]->typeId, typeId) == 0)
	  return protos[i];
      return (Proto *)0;
    }
};
static stack<context *> contextStack;
static context *ctx;

/* looks up a PROTO node with given typeId, first in the current context,
 * next in the previous contexts if not found in the current context. */
static SFNode *LookupProto(char *typeId)
{
  SFNode *proto = ctx->lookup_proto(typeId);
  int i = contextStack.size-1;
  while (!proto && i>=0) {
    proto = contextStack[i]->lookup_proto(typeId);
    i--;
  }
  return proto;
}

/* parsing routines, implemented at the bottom of this file (below
 * the yacc generated code. */
static fieldValueBase ParseValue(fieldType type);
static void SkipFieldValue(void);

/* needed while parsing interface declarations */
static array<interfaceDeclaration> interface(20);

/* for temporarily storing node class Ids while parsing */
static array<char *> classIds(20);

/* returns the field value type for the given field Id within the
 * current node. */
static interfaceDeclaration *GetField(char *fieldId)
{
  interfaceDeclaration *field;

  if (!(field = ctx->curnode->get_field(fieldId))) {
    Error(NULL, "%s nodes don't have a '%s' field",
	  ctx->curnode->typeId, fieldId);
    SkipFieldValue();
  }
  return field;
}

/* handles fieldId - value pairs when the value is not an SFNode or
 * MFNode (SFNode and MFNode values are parsed using the yacc generated
 * parser, other "simple" types are parsed with ParseValue()) */
static void NonNodeFieldIdValuePair(char *fieldId)
{
  interfaceDeclaration *field;
  fieldValue value;

  if (!(field = GetField(fieldId)))
    return;
  value = ParseValue(field->type);

  field->initialize(value);
  ctx->curnode->clock++;
}

/* handles field Id-value pairs when the value is of type SFNode or
 * MFNode. */
static void NodeFieldIdValuePair(char *fieldId, fieldValue value)
{
  interfaceDeclaration *field;

  if (!(field = GetField(fieldId)))
    return;

  if (field->type!=tSFNode && field->type!=tMFNode) {
    Fatal(1, NULL, "%s value required for a %s node %s field but got a %s",
	  fieldTypeName(field->type), ctx->curnode->typeId, fieldId, fieldTypeName(fieldType(value)));
    return;
  }

  /* convert single-node values between SFNode and MFNode if necessary */
  if (field->type == tSFNode && fieldType(value) == tMFNode) {
    SFNode *sfnode = ((MFNode&)value)[0];
    delete (MFNode *)value;
    value = new SFNode*(sfnode);
  }

  else if (field->type == tMFNode && fieldType(value) == tSFNode) {
    MFNode *mfnodep = new MFNode;
    mfnodep->append((SFNode *&)value);
    delete (SFNode **)value;
    value = mfnodep;
  }

  field->initialize(value);
  ctx->curnode->clock++;
}
%}
%union { /* stack type */
	char	*s;
	double	f;
	long	i;
	fieldType t;
        fieldValueBase val;
	SFNode *node;
        MFNode *nodes;
	MFString *strings;
}
%token		tVRML tNODECATALOG
%token 		tDEF tUSE tPROTO teventIn teventOut tfield texposedField 
%token		tROUTE tTO tIS tEXTERNPROTO tTRUE tFALSE tNULL tScript
%token	<t>	tSIMPLEFIELD tSFNODE tMFNODE
%token	<s>	tID tSTRING
%token	<f>	tFLOAT
%token	<i>	tINT tHEX
%type	<t>	fieldType
%type	<s>	eventInId eventOutId fieldId nodeTypeId nodeNameId Id
%type	<val>	sfnodeValue mfnodeValue
%type	<node>	node nodeStatement
%type	<nodes> nodeStatements
%type	<strings> URLList mfstringValue strings
%%
/* ***************************** general ****************************** */
file:	  vrmlHeader vrmlScene
        | nodeCatalogHeader nodeCatalog
        ;
nodeCatalogHeader: tNODECATALOG
	{
	  fprintf(stderr, "Reading node catalog ...\n"); 
	}
        ;
nodeCatalog: nodedescriptions
        ;
nodedescriptions: nodedescription nodedescriptions
        | /* empty */
        ;
nodedescription: nodeTypeId '{' interfaceDeclarations '}'
	{
	  SFNode *node = new SFNode($1, interface.size, &interface[0]);
	  interface.size = 0;
	  RegisterBuiltin(node);
	}
        | nodeTypeId '[' parentNodeClasses ']' '{' interfaceDeclarations '}'
        {
	  SFNode *node = new SFNode($1, interface.size, &interface[0]);
	  node->parentClassIds = classIds;
	  interface.size = 0;
	  classIds.size = 0;
	  RegisterBuiltin(node);
        }
        ;
parentNodeClasses: nodeTypeId
        {
	  classIds.append($1);
        }
        | nodeTypeId parentNodeClasses
        {
	  classIds.append($1);
        }
        | /* empty */
        ;
vrmlHeader: tVRML
	{
	  fprintf(stderr, "Parsing VRML file '%s' ...\n", yyfilename);
	}
        ;
vrmlScene: statements 
	;
statements: statement 
	| statement statements 
	| /* empty */ 
	;
statement: nodeStatement {} 
        | protoStatement {}
        | routeStatement {}
	;
nodeStatement: node
        {
	  $$ = $1; 
	  if ($$)
	    if (!ctx->curnode) ctx->sceneGraph->append($$); /* root node */ 
	}
	| tDEF nodeNameId node
        {
	  $$ = $3;
	  if ($$) {
	    $$->nameIndex = ctx->namedNodes.add($2, $3);
	    $$->nameId = $2;
	  }
	  if ($$)
	    if (!ctx->curnode) ctx->sceneGraph->append($$); /* root node */ 
	}
	| tUSE nodeNameId
	{
	  int i = ctx->namedNodes($2);
	  if (i < 0) 
	    Error(NULL, "undefined node %s", $2);
	  else
	    $$ = ctx->namedNodes[i];
	  if ($$)
	    if (!ctx->curnode) ctx->sceneGraph->append($$); /* root node */ 
	}
	;
protoStatement: proto 
	| externproto 
	;
protoStatements: protoStatement 
	| protoStatement protoStatements 
	| /* empty */ 
	;
proto:    protoInterface '{' protoBody '}' 
	{
	  Proto *proto = new Proto(ctx->master, ctx->sceneGraph, ctx->namedNodes);
	  ctx = contextStack.pop();
	  ctx->protos.append(proto);
	}
	;
protoInterface: tPROTO nodeTypeId '[' interfaceDeclarations ']'
        {
	  if (ctx->lookup_proto($2))
	    Error(NULL, "duplicate PROTO '%s'", $2);

	  contextStack.push(ctx); ctx = new context;
	  ctx->master = new SFNode($2, interface.size, &interface[0]);
	  interface.size = 0;
	}
        ;
protoBody: protoStatements nodeStatement statements
	;
interfaceDeclarations: interfaceDeclaration interfaceDeclarations 
	| /* empty */ 
	;
interfaceDeclaration: teventIn fieldType eventInId 
	{
	  interfaceDeclaration decl(keventIn, $2, $3); 
	  interface.append(decl);
	}
	| teventOut fieldType eventOutId
	{
	  interfaceDeclaration decl(keventOut, $2, $3); 
	  interface.append(decl);
	}
	| tfield fieldTypeIdValueTriple
	{
	  interface[interface.size-1].kind = kfield;
	}
	| texposedField fieldTypeIdValueTriple
	{
	  interface[interface.size-1].kind = kexposedField; 
	}
	;
fieldTypeIdValueTriple:
	  tSFNODE fieldId sfnodeValue	/* single node fieldValue */
	{
	  interfaceDeclaration decl(kfield, $1, $2, $3); 
	  interface.append(decl);
	}
	| tSFNODE '[' nodeTypeId ']' fieldId sfnodeValue	/* single node fieldValue */
	{
	  interfaceDeclaration decl(kfield, $1, $5, $6); 
	  decl.nodeClassId = $3;
	  interface.append(decl);
	}
	| tMFNODE fieldId mfnodeValue	/* node array fieldValue */
	{ 
	  interfaceDeclaration decl(kfield, $1, $2, $3); 
	  interface.append(decl);
	}
	| tMFNODE '[' nodeTypeId ']' fieldId mfnodeValue	/* node array fieldValue */
	{
	  interfaceDeclaration decl(kfield, $1, $5, $6); 
	  decl.nodeClassId = $3;
	  interface.append(decl);
	}
	| tSIMPLEFIELD fieldId '['	/* other array fieldValue */
	{
	  UnputToken(); 
	  interfaceDeclaration decl(kfield, $1, $2, ParseValue($1)); 
	  interface.append(decl);
	}
	| tSIMPLEFIELD fieldId fieldValueAtom	/* other simple fieldValue */
	{
	  UnputToken(); 
	  interfaceDeclaration decl(kfield, $1, $2, ParseValue($1)); 
	  interface.append(decl);
	}
	;
externproto: tEXTERNPROTO nodeTypeId '[' externInterfaceDeclarations ']' URLList 
	{
	  if (ctx->lookup_proto($2))
	    Error(NULL, "duplicate PROTO '%s'", $2);

	  SFNode *master = new SFNode($2, interface.size, &interface[0]);
	  interface.size = 0;

	  Warning(NULL, "can't yet fetch implementation for '%s'", $2);

	  ctx->protos.append(new Proto(master, (MFNode *)0, namedNodeTable(0)));
	}
	;
externInterfaceDeclarations: 
	  externInterfaceDeclaration externInterfaceDeclarations 
	| /* empty */
	;
externInterfaceDeclaration: teventIn fieldType eventInId 
	{
	  interfaceDeclaration decl(keventIn, $2, $3); 
	  interface.append(decl);
	}
	| teventOut fieldType eventOutId 
	{
	  interfaceDeclaration decl(keventOut, $2, $3); 
	  interface.append(decl);
	}
	| tfield fieldType fieldId 
	{
	  interfaceDeclaration decl(kfield, $2, $3); 
	  interface.append(decl);
	}
	| texposedField fieldType fieldId 
	{
	  interfaceDeclaration decl(kexposedField, $2, $3); 
	  interface.append(decl);
	}
	;
URLList:  mfstringValue
	;
routeStatement: tROUTE nodeNameId '.' eventOutId tTO nodeNameId '.' eventInId 
	;

/* ************************* nodes ***************************** */
node:	  scriptNodeTypeId '{' scriptBody '}'
	{ $$ = ctx->curnode; ctx->curnode = ctx->nodeStack.pop(); }
        | otherNodeTypeId '{' nodeBody '}' 
        { $$ = ctx->curnode; ctx->curnode = ctx->nodeStack.pop(); }
	;
scriptNodeTypeId: tScript
	{
	  ctx->nodeStack.push(ctx->curnode); 
	  ctx->curnode = LookupBuiltin("Script")->instantiate();
	}
	;
otherNodeTypeId: nodeTypeId
	{
	  ctx->nodeStack.push(ctx->curnode);

	  SFNode *prototype = LookupBuiltin($1);
	  if (!prototype)
	    prototype = LookupProto($1);
	  if (!prototype)
	    Fatal(1, NULL, "Unknown node type '%s'", $1);

	  ctx->curnode = prototype->instantiate();
	}
	;
scriptBody: scriptBodyElement scriptBody
	| /* empty */
	;
scriptBodyElement: nodeBodyElement 
	| scriptInterfaceDeclaration
	;
scriptInterfaceDeclaration: 
	  teventIn scriptEventInDeclaration
	| teventOut scriptEventOutDeclaration
	| tfield scriptFieldDeclaration
	;
scriptEventInDeclaration: fieldType eventInId {}
        | fieldType eventInId tIS eventInId {}
	;
scriptEventOutDeclaration: fieldType eventOutId {}
        | fieldType eventOutId tIS eventOutId {}
	;
scriptFieldDeclaration: fieldTypeIdValueTriple {}
        | tSFNODE fieldId tIS fieldId {}
        | tMFNODE fieldId tIS fieldId {}
        | tSIMPLEFIELD fieldId tIS fieldId {}
	;
nodeBody: nodeBodyElement 
	| nodeBodyElement nodeBody 
	| /* empty */
	;
nodeBodyElement: fieldIdValuePair
	| fieldId tIS fieldId
        { 
	  interfaceDeclaration *field = GetField($1);

	  interfaceDeclaration *ifield = 
	    ctx->master ? ctx->master->get_field($3) : 0;
	  if (!ifield)
	    Error(NULL, "can't find PROTO interface field '%s'", $3);

	  if (field && ifield) {
	    /* check types */
	    if (field->type != ifield->type)
	      Error(NULL, "field types %s and %s don't match", 
		    fieldTypeName(field->type), fieldTypeName(ifield->type));
	    else
	      field->redirect(ctx->master, ifield);
	  }
	}
	| eventInId tIS eventInId
        {
	  interfaceDeclaration *ieventIn = 
	    ctx->master ? ctx->master->get_eventIn($3) : 0;
	  if (!ieventIn)
	    Error(NULL, "can't find PROTO interface eventIn '%s'", $3);
	}
	| eventOutId tIS eventOutId
        {
	  interfaceDeclaration *ieventOut = 
	    ctx->master ? ctx->master->get_eventOut($3) : 0;
	  if (!ieventOut)
	    Error(NULL, "can't find PROTO interface eventOut '%s'", $3);
	}
	| routeStatement 
	| protoStatement 
	;
fieldIdValuePair: fieldId fieldValueAtom /* single non-node fieldValue */
        { UnputToken(); NonNodeFieldIdValuePair($1); }
	| fieldId '[' fieldValueAtom	/* non-empty non-node array fieldValue */
	{ UnputToken(); UnputString("["); NonNodeFieldIdValuePair($1); }
	| fieldId '[' ']'		/* empty array fieldValue */
	{ UnputString("[]"); NonNodeFieldIdValuePair($1); }
        | fieldId sfnodeValue		/* single node fieldValue */
	{ NodeFieldIdValuePair($1, $2); }
        | fieldId mfnodeValue		/* non-empty node array fieldValue */
	{ NodeFieldIdValuePair($1, $2); }
	;
nodeNameId: Id
	;
nodeTypeId: Id 
        | tScript
        { $$ = "Script"; }
	;
fieldId: Id 
	;
eventInId: Id 
	;
eventOutId: Id
	;
Id:	tID
	;

/* ********************** Field Types and Values *********************** */
fieldType: tSIMPLEFIELD
	| tSFNODE
	| tMFNODE
	;
fieldValueAtom: tTRUE {} | tFALSE {} | tSTRING {} | tFLOAT {} | tINT {} | tHEX {}
        ;
mfnodeValue: nodeStatement
        { fieldValue val = new MFNode; if ($1) ((MFNode&)val).append($1); $$ = val; }
	| '[' ']' 
        { fieldValue val = new MFNode; $$ = val; }
	| '[' nodeStatements ']' 
        { fieldValue val = $2; $$ = val; }
	;
nodeStatements: nodeStatement
	{ $$ = new MFNode; if ($1) $$->append($1); }
	| nodeStatements nodeStatement
	{ $$ = $1; if ($2) $$->append($2); }
	;
sfnodeValue: nodeStatement
        { fieldValue val = new SFNode *($1); $$ = val; }
	| tNULL
        { fieldValue val = new SFNode *(0); $$ = val; }
	;
mfstringValue: tSTRING
        { $$ = new MFString; $$->append((SFString)$1); }
	| '[' ']' 
        { $$ = new MFString; }
	| '[' strings ']' 
	{ $$ = $2; }
	;
strings:  tSTRING
        { $$ = new MFString; $$->append((SFString)$1); }
	| strings tSTRING
        { $$ = $1; $$->append((SFString)$2); }
	;
%%
/* ********************************************************************** */ 
/* returns TRUE if the token is something that can occur in a field value
 * and FALSE if it is something that can not. */
static int IsFieldValueAtomOrSquareBracket(int token)
{
  switch (token) {
  case tTRUE: case tFALSE: case tNULL: case tSTRING: 
  case tFLOAT: case tINT: case tHEX: case '[': case ']':
    return TRUE;
  }
  return FALSE;
}

/* skips the remainder of a field value, for recovering after
 * fieldId lookup failure. */
static void SkipFieldValue(void)
{
  while (IsFieldValueAtomOrSquareBracket(yylex())) {}
  UnputToken();	/* we read one token too much */
}

#ifdef NEVER
/* skips the remainder of a node body, for recovering after a
 * nodeTypeId lookup failure. */
static void SkipNodeBody(void)
{
  int nestlevel = 1;	/* the first '{' has been consumed already */
  fprintf(stderr, "SkipNodeBody: untested.\n");

  while (nestlevel > 0) {
    switch (yylex()) {
    case '{': nestlevel++; break;
    case '}': nestlevel--; break;
    }
  }
  UnputToken();	 /* put back the last '}' so the parser
		  * doesn't get confused. */
}
#endif

/* ******************* field value parsing routines ********************** */
/* We can't leave field value parsing to yacc, because the type of a field
 * value to be parsed depends on the semantics of the field being parsed
 * (field value parsing is not context-free, see the DIS appendix.A). Only
 * non-trivial SFNode and MFNode values are left to yacc since they can
 * be distinguished from other value types anyways and are hard to do
 * ourselves. */
static double ParseFloat(void)
{
  switch (yylex()) {
  case tFLOAT: 	return yylval.f;
  case tINT:	return (double)yylval.i;
  default:    	yyerror("parse error (expecting a float)");
  }
  return 0.;
}

static long ParseInt(void)
{
  switch (yylex()) {
  case tINT:	return yylval.i;
  case tHEX:	return yylval.i;
  default:	yyerror("parse error (expecting an integer)");
  }
  return 0;
}

static SFBool ParseSFBool(void)
{
  switch (yylex()) {
  case tTRUE: 	return TRUE;
  case tFALSE: 	return FALSE;
  default:	yyerror("parse error (expecting a boolean)");
  }
  return FALSE;
}
 
static SFColor ParseSFColor(void) 
{
  SFColor col;
  col.r = ParseFloat();
  col.g = ParseFloat();
  col.b = ParseFloat();
  return col;
}
 
static SFFloat ParseSFFloat(void)
{
  return (SFFloat)ParseFloat();
}
 
static SFInt32 ParseSFInt32(void) 
{ 
  return (SFInt32)ParseInt();
}

static SFImage *ParseSFImage(void)
{
  SFImage *img = new SFImage(ParseSFInt32(), ParseSFInt32(), ParseSFInt32());
  for (int i=0; i<img->width * img->height; i++)
    img->pixels[i] = ParseSFInt32();

  return img;
}
  
static SFNode *ParseSFNode(void) 
{ 
  switch (yylex()) {
  case tNULL:
    return (SFNode *)NULL;
  default:
    Fatal(-1, "ParseSFNode", "can't parse non-null nodes");
  }
  return (SFNode *)NULL;
}
 
static SFRotation ParseSFRotation(void) 
{ 
  SFRotation rot;
  rot.x = ParseFloat();
  rot.y = ParseFloat();
  rot.z = ParseFloat();
  rot.radians = ParseFloat();
  return rot;
}
 
static SFString ParseSFString(void) 
{ 
  if (yylex() == tSTRING)
    return (SFString)yylval.s;
  else
    yyerror("parse error (expecting a string)");
  return (SFString)NULL;
}
 
static SFTime ParseSFTime(void)
{
  return (SFTime)ParseFloat();
}
 
static SFVec2f ParseSFVec2f(void)
{ 
  SFVec2f v;
  v.s = ParseFloat();
  v.t = ParseFloat();
  return v;
}

static SFVec3f ParseSFVec3f(void)
{
  SFVec3f v;
  v.x = ParseFloat();
  v.y = ParseFloat();
  v.z = ParseFloat();
  return v;
}

#define PARSEARRAY(type, parsel) { 	\
  type *p = new type;			\
					\
  switch (yylex()) {			\
  case '[':				\
    while (yylex() != ']') {		\
      UnputToken();			\
      p->append(parsel());		\
    }					\
    break;				\
  default:				\
    UnputToken();			\
    p->append(parsel());		\
  }					\
					\
  return p;				\
}

static MFColor *ParseMFColor(void)
{
  PARSEARRAY(MFColor, ParseSFColor);
}

static MFFloat *ParseMFFloat(void) 
{
  PARSEARRAY(MFFloat, ParseSFFloat);
} 

static MFInt32 *ParseMFInt32(void) 
{ 
  PARSEARRAY(MFInt32, ParseSFInt32);
} 

static MFNode *ParseMFNode(void)
{
  PARSEARRAY(MFNode, ParseSFNode);
}
 
static MFRotation *ParseMFRotation(void)
{
  PARSEARRAY(MFRotation, ParseSFRotation);
}

static MFString *ParseMFString(void) 
{ 
  PARSEARRAY(MFString, ParseSFString);
}

static MFTime *ParseMFTime(void)
{
  PARSEARRAY(MFTime, ParseSFTime);
}
 
static MFVec2f *ParseMFVec2f(void) 
{ 
  PARSEARRAY(MFVec2f, ParseSFVec2f);
}

static MFVec3f *ParseMFVec3f(void)
{
  PARSEARRAY(MFVec3f, ParseSFVec3f);
}

/* parses a value of the given type */
static fieldValueBase ParseValue(fieldType type)
{
  fieldValue val;	/* not a fieldValueBase, because we'd like to use
			 * the type-dependent constructors that are not possible 
			 * with fieldValueBase. */

  switch(type) {
  case tSFBool:		val = new SFBool(ParseSFBool());	break;
  case tSFFloat:	val = new SFFloat(ParseSFFloat());	break; 
  case tSFInt32:	val = new SFInt32(ParseSFInt32());	break; 
  case tSFTime:		val = new SFTime(ParseSFTime());	break; 
  case tSFColor:	val = new SFColor(ParseSFColor());	break;
  case tSFRotation: 	val = new SFRotation(ParseSFRotation()); break;
  case tSFVec2f:	val = new SFVec2f(ParseSFVec2f());	break; 
  case tSFVec3f:	val = new SFVec3f(ParseSFVec3f());	break;
  case tSFString:	val = new SFString(ParseSFString());	break;
  case tSFNode:		val = new SFNode*(ParseSFNode()); 	break;
  case tSFImage:	val = ParseSFImage();			break;
  case tMFColor:	val = ParseMFColor();			break; 
  case tMFFloat:	val = ParseMFFloat();			break; 
  case tMFInt32:	val = ParseMFInt32();			break; 
  case tMFNode:		val = ParseMFNode();			break; 
  case tMFRotation: 	val = ParseMFRotation(); 		break; 
  case tMFString:	val = ParseMFString(); 			break; 
  case tMFTime:		val = ParseMFTime();			break; 
  case tMFVec2f:	val = ParseMFVec2f();			break; 
  case tMFVec3f:	val = ParseMFVec3f();			break; 
  default:
    Fatal(-1, "ParseValue", "invalid type %d\n", type);
  }

  return val;
}

/* ************************************************************************** */
/* parses the given VRML file or node catalog file and returns the
 * constructed scene graph. */
MFNode *VRMLParse(char *filename)
{
  lineno = 0;

  if (!filename) 
    yyfilename = "<stdin>";
  else {
    char *slash = strrchr(filename, '/');
    if (!slash)
      yyfilename = strdup(filename);
    else
      yyfilename = strdup(slash+1);
  }

  char *dot = strrchr(yyfilename, '.');
  char *ext = "";
  if (dot) 
    ext = dot+1;

  int ispipe = 0;
  if (!filename) {
    yyin = stdin;
  } else if (strcmp(ext, "gz") == 0) {
    char *cmd = (char *)malloc(strlen(filename) + 20);    
    sprintf(cmd, "gunzip < %s", filename);
    yyin = popen(cmd, "r");
    free(cmd);
    ispipe = 1;
  } else if (strcmp(ext, "Z") == 0) {
    char *cmd = (char *)malloc(strlen(filename) + 20);    
    sprintf(cmd, "uncompress < %s", filename);
    yyin = popen(cmd, "r");
    free(cmd);
    ispipe = 1;
  } else if (filename[0] == '<') {
    yyin = popen(filename+1, "r");
    ispipe = 1;
  } else
    yyin = fopen(filename, "r");

  if (!yyin) {
    Error(NULL, "Can't open file '%s' for reading", yyfilename);
    return 0;
  }

  ctx = new context;

  lineno = 1;
  yyparse();
  lineno = 0;

  if (ispipe)
    pclose(yyin);
  else if (yyin != stdin)
    fclose(yyin);

  return ctx->sceneGraph;
}

