/* yacc.y parser for VRML97. C++ code!!! */ %{ #include #include #include #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 namedNodes; /* DEF'ed nodes within the context */ array protos; /* PROTO and EXTERNPROTO nodes within the context */ stack 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; itypeId, typeId) == 0) return protos[i]; return (Proto *)0; } }; static stack 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 interface(20); /* for temporarily storing node class Ids while parsing */ static array 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 tSIMPLEFIELD tSFNODE tMFNODE %token tID tSTRING %token tFLOAT %token tINT tHEX %type fieldType %type eventInId eventOutId fieldId nodeTypeId nodeNameId Id %type sfnodeValue mfnodeValue %type node nodeStatement %type nodeStatements %type 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; iwidth * 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 = ""; 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; }