/* ** zcc_compile.cpp ** **--------------------------------------------------------------------------- ** Copyright -2016 Randy Heit ** Copyright 2016 Christoph Oelckers ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** 3. The name of the author may not be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **--------------------------------------------------------------------------- ** */ #include "c_console.h" #include "filesystem.h" #include "zcc_parser.h" #include "zcc-parse.h" #include "zcc_compile.h" #include "printf.h" #include "symbols.h" FSharedStringArena VMStringConstants; int GetIntConst(FxExpression *ex, FCompileContext &ctx) { ex = new FxIntCast(ex, false); ex = ex->Resolve(ctx); return ex ? static_cast(ex)->GetValue().GetInt() : 0; } double GetFloatConst(FxExpression *ex, FCompileContext &ctx) { ex = new FxFloatCast(ex); ex = ex->Resolve(ctx); return ex ? static_cast(ex)->GetValue().GetFloat() : 0; } const char * ZCCCompiler::GetStringConst(FxExpression *ex, FCompileContext &ctx) { ex = new FxStringCast(ex); ex = ex->Resolve(ctx); if (!ex) return ""; // The string here must be stored in a persistent place that lasts long enough to have it processed. return AST.Strings.Alloc(static_cast(ex)->GetValue().GetString())->GetChars(); } int ZCCCompiler::IntConstFromNode(ZCC_TreeNode *node, PContainerType *cls) { FCompileContext ctx(OutNamespace, cls, false, mVersion); FxExpression *ex = new FxIntCast(ConvertNode(node), false); ex = ex->Resolve(ctx); if (ex == nullptr) return 0; if (!ex->isConstant()) { ex->ScriptPosition.Message(MSG_ERROR, "Expression is not constant"); return 0; } return static_cast(ex)->GetValue().GetInt(); } FString ZCCCompiler::StringConstFromNode(ZCC_TreeNode *node, PContainerType *cls) { FCompileContext ctx(OutNamespace, cls, false, mVersion); FxExpression *ex = new FxStringCast(ConvertNode(node)); ex = ex->Resolve(ctx); if (ex == nullptr) return ""; if (!ex->isConstant()) { ex->ScriptPosition.Message(MSG_ERROR, "Expression is not constant"); return ""; } return static_cast(ex)->GetValue().GetString(); } ZCC_MixinDef *ZCCCompiler::ResolveMixinStmt(ZCC_MixinStmt *mixinStmt, EZCCMixinType type) { for (auto mx : Mixins) { if (mx->mixin->NodeName == mixinStmt->MixinName) { if (mx->mixin->MixinType != type) { Error(mixinStmt, "Mixin %s is a %s mixin cannot be used here.", FName(mixinStmt->MixinName).GetChars(), GetMixinTypeString(type)); return nullptr; } return mx->mixin; } } Error(mixinStmt, "Mixin %s does not exist.", FName(mixinStmt->MixinName).GetChars()); return nullptr; } //========================================================================== // // ZCCCompiler :: ProcessClass // //========================================================================== void ZCCCompiler::ProcessClass(ZCC_Class *cnode, PSymbolTreeNode *treenode) { ZCC_ClassWork *cls = nullptr; // If this is a class extension, put the new node directly into the existing class. if (cnode->Flags == ZCC_Extension) { for (auto clss : Classes) { if (clss->NodeName() == cnode->NodeName) { cls = clss; break; } } if (cls == nullptr) { Error(cnode, "Class %s cannot be found in the current translation unit.", FName(cnode->NodeName).GetChars()); return; } } else { Classes.Push(new ZCC_ClassWork(static_cast(cnode), treenode)); cls = Classes.Last(); } auto node = cnode->Body; PSymbolTreeNode *childnode; ZCC_Enum *enumType = nullptr; // [pbeta] Handle mixins here for the sake of simplifying things. if (node != nullptr) { bool mixinError = false; TArray mixinStmts; mixinStmts.Clear(); // Gather all mixin statement nodes. do { if (node->NodeType == AST_MixinStmt) { mixinStmts.Push(static_cast(node)); } node = node->SiblingNext; } while (node != cnode->Body); for (auto mixinStmt : mixinStmts) { ZCC_MixinDef *mixinDef = ResolveMixinStmt(mixinStmt, ZCC_Mixin_Class); if (mixinDef == nullptr) { mixinError = true; continue; } // Insert the mixin if there's a body. If not, just remove this node. if (mixinDef->Body != nullptr) { auto newNode = TreeNodeDeepCopy(&AST, mixinDef->Body, true); if (mixinStmt->SiblingNext != mixinStmt && mixinStmt->SiblingPrev != mixinStmt) { auto prevSibling = mixinStmt->SiblingPrev; auto nextSibling = mixinStmt->SiblingNext; auto newFirst = newNode; auto newLast = newNode->SiblingPrev; newFirst->SiblingPrev = prevSibling; newLast->SiblingNext = nextSibling; prevSibling->SiblingNext = newFirst; nextSibling->SiblingPrev = newLast; } if (cnode->Body == mixinStmt) { cnode->Body = newNode; } } else { if (mixinStmt->SiblingNext != mixinStmt && mixinStmt->SiblingPrev != mixinStmt) { auto prevSibling = mixinStmt->SiblingPrev; auto nextSibling = mixinStmt->SiblingNext; prevSibling->SiblingNext = nextSibling; nextSibling->SiblingPrev = prevSibling; if (cnode->Body == mixinStmt) { cnode->Body = nextSibling; } } else if (cnode->Body == mixinStmt) { cnode->Body = nullptr; } } } mixinStmts.Clear(); if (mixinError) { return; } } node = cnode->Body; // Need to check if the class actually has a body. if (node != nullptr) do { switch (node->NodeType) { case AST_MixinStmt: assert(0 && "Unhandled mixin statement in class parsing loop. If this has been reached, something is seriously wrong"); Error(node, "Internal mixin error."); break; case AST_Struct: case AST_ConstantDef: case AST_Enum: if ((childnode = AddTreeNode(static_cast(node)->NodeName, node, &cls->TreeNodes))) { switch (node->NodeType) { case AST_Enum: enumType = static_cast(node); cls->Enums.Push(enumType); break; case AST_Struct: if (static_cast(node)->Flags & VARF_Native) { Error(node, "Cannot define native structs inside classes"); static_cast(node)->Flags &= ~VARF_Native; } ProcessStruct(static_cast(node), childnode, cls->cls); break; case AST_ConstantDef: cls->Constants.Push(static_cast(node)); cls->Constants.Last()->Type = enumType; break; default: assert(0 && "Default case is just here to make GCC happy. It should never be reached"); } } break; case AST_Property: cls->Properties.Push(static_cast(node)); break; case AST_FlagDef: cls->FlagDefs.Push(static_cast(node)); break; case AST_VarDeclarator: cls->Fields.Push(static_cast(node)); break; case AST_EnumTerminator: enumType = nullptr; break; case AST_States: cls->States.Push(static_cast(node)); break; case AST_FuncDeclarator: cls->Functions.Push(static_cast(node)); break; case AST_Default: cls->Defaults.Push(static_cast(node)); break; case AST_StaticArrayStatement: if (AddTreeNode(static_cast(node)->Id, node, &cls->TreeNodes)) { cls->Arrays.Push(static_cast(node)); } break; default: assert(0 && "Unhandled AST node type"); break; } node = node->SiblingNext; } while (node != cnode->Body); } //========================================================================== // // ZCCCompiler :: ProcessMixin // //========================================================================== void ZCCCompiler::ProcessMixin(ZCC_MixinDef *cnode, PSymbolTreeNode *treenode) { ZCC_MixinWork *cls = new ZCC_MixinWork(cnode, treenode); auto node = cnode->Body; // Need to check if the mixin actually has a body. if (node != nullptr) do { if (cnode->MixinType == ZCC_Mixin_Class) { switch (node->NodeType) { case AST_Struct: case AST_ConstantDef: case AST_Enum: case AST_Property: case AST_FlagDef: case AST_VarDeclarator: case AST_EnumTerminator: case AST_States: case AST_FuncDeclarator: case AST_Default: case AST_StaticArrayStatement: break; default: assert(0 && "Unhandled AST node type"); break; } } node = node->SiblingNext; } while (node != cnode->Body); Mixins.Push(cls); } //========================================================================== // // ZCCCompiler :: ProcessStruct // //========================================================================== void ZCCCompiler::ProcessStruct(ZCC_Struct *cnode, PSymbolTreeNode *treenode, ZCC_Class *outer) { ZCC_StructWork* cls = nullptr; // If this is a struct extension, put the new node directly into the existing class. if (cnode->Flags == ZCC_Extension) { for (auto strct : Structs) { if (strct->NodeName() == cnode->NodeName) { cls = strct; break; } } if (cls == nullptr) { Error(cnode, "Struct %s cannot be found in the current translation unit.", FName(cnode->NodeName).GetChars()); return; } } else { Structs.Push(new ZCC_StructWork(static_cast(cnode), treenode, outer)); cls = Structs.Last(); } auto node = cnode->Body; PSymbolTreeNode *childnode; ZCC_Enum *enumType = nullptr; // Need to check if the struct actually has a body. if (node != nullptr) do { switch (node->NodeType) { case AST_ConstantDef: case AST_Enum: if ((childnode = AddTreeNode(static_cast(node)->NodeName, node, &cls->TreeNodes))) { switch (node->NodeType) { case AST_Enum: enumType = static_cast(node); cls->Enums.Push(enumType); break; case AST_ConstantDef: cls->Constants.Push(static_cast(node)); cls->Constants.Last()->Type = enumType; break; default: assert(0 && "Default case is just here to make GCC happy. It should never be reached"); } } break; case AST_VarDeclarator: cls->Fields.Push(static_cast(node)); break; case AST_FuncDeclarator: cls->Functions.Push(static_cast(node)); break; case AST_EnumTerminator: enumType = nullptr; break; case AST_StaticArrayStatement: if (AddTreeNode(static_cast(node)->Id, node, &cls->TreeNodes)) { cls->Arrays.Push(static_cast(node)); } break; default: assert(0 && "Unhandled AST node type"); break; } node = node->SiblingNext; } while (node != cnode->Body); } //========================================================================== // // ZCCCompiler Constructor // //========================================================================== ZCCCompiler::ZCCCompiler(ZCC_AST &ast, DObject *_outer, PSymbolTable &_symbols, PNamespace *_outnamespc, int lumpnum, const VersionInfo &ver) : mVersion(ver), Outer(_outer), ConvertClass(nullptr), GlobalTreeNodes(&_symbols), OutNamespace(_outnamespc), AST(ast), Lump(lumpnum) { FScriptPosition::ResetErrorCounter(); // Group top-level nodes by type if (ast.TopNode != NULL) { ZCC_TreeNode *node = ast.TopNode; PSymbolTreeNode *tnode = nullptr; // [pbeta] Anything that must be processed before classes, structs, etc. should go here. do { switch (node->NodeType) { // [pbeta] Mixins must be processed before everything else. case AST_MixinDef: if ((tnode = AddTreeNode(static_cast(node)->NodeName, node, GlobalTreeNodes))) { ProcessMixin(static_cast(node), tnode); break; } break; default: break; // Shut GCC up. } node = node->SiblingNext; } while (node != ast.TopNode); node = ast.TopNode; PType *enumType = nullptr; ZCC_Enum *zenumType = nullptr; do { switch (node->NodeType) { case AST_MixinDef: // [pbeta] We already processed mixins, ignore them here. break; case AST_Class: // a class extension should not check the tree node symbols. if (static_cast(node)->Flags == ZCC_Extension) { ProcessClass(static_cast(node), tnode); break; } goto common; case AST_Struct: if (static_cast(node)->Flags == ZCC_Extension) { ProcessStruct(static_cast(node), tnode, nullptr); break; } goto common; common: case AST_ConstantDef: case AST_Enum: if ((tnode = AddTreeNode(static_cast(node)->NodeName, node, GlobalTreeNodes))) { switch (node->NodeType) { case AST_Enum: zenumType = static_cast(node); enumType = NewEnum(zenumType->NodeName, OutNamespace); OutNamespace->Symbols.AddSymbol(Create(zenumType->NodeName, enumType)); break; case AST_Class: ProcessClass(static_cast(node), tnode); break; case AST_Struct: ProcessStruct(static_cast(node), tnode, nullptr); break; case AST_ConstantDef: Constants.Push(static_cast(node)); Constants.Last()->Type = zenumType; break; default: assert(0 && "Default case is just here to make GCC happy. It should never be reached"); } } break; case AST_EnumTerminator: zenumType = nullptr; break; default: assert(0 && "Unhandled AST node type"); break; } node = node->SiblingNext; } while (node != ast.TopNode); } } ZCCCompiler::~ZCCCompiler() { for (auto s : Structs) { delete s; } for (auto c : Classes) { delete c; } Structs.Clear(); Classes.Clear(); } //========================================================================== // // ZCCCompiler :: AddTreeNode // // Keeps track of definition nodes by their names. Ensures that all names // in this scope are unique. // //========================================================================== PSymbolTreeNode *ZCCCompiler::AddTreeNode(FName name, ZCC_TreeNode *node, PSymbolTable *treenodes, bool searchparents) { PSymbol *check = treenodes->FindSymbol(name, searchparents); if (check != NULL) { assert(check->IsA(RUNTIME_CLASS(PSymbolTreeNode))); Error(node, "Attempt to redefine '%s'", name.GetChars()); Error(static_cast(check)->Node, " Original definition is here"); return nullptr; } else { auto sy = Create(name, node); treenodes->AddSymbol(sy); return sy; } } //========================================================================== // // ZCCCompiler :: Warn // // Prints a warning message, and increments WarnCount. // //========================================================================== void ZCCCompiler::Warn(ZCC_TreeNode *node, const char *msg, ...) { va_list argptr; va_start(argptr, msg); MessageV(node, TEXTCOLOR_ORANGE, msg, argptr); va_end(argptr); FScriptPosition::WarnCounter++; } //========================================================================== // // ZCCCompiler :: Error // // Prints an error message, and increments ErrorCount. // //========================================================================== void ZCCCompiler::Error(ZCC_TreeNode *node, const char *msg, ...) { va_list argptr; va_start(argptr, msg); MessageV(node, TEXTCOLOR_RED, msg, argptr); va_end(argptr); FScriptPosition::ErrorCounter++; } //========================================================================== // // ZCCCompiler :: MessageV // // Prints a message, annotated with the source location for the tree node. // //========================================================================== void ZCCCompiler::MessageV(ZCC_TreeNode *node, const char *txtcolor, const char *msg, va_list argptr) { FString composed; composed.Format("%s%s, line %d: ", txtcolor, node->SourceName->GetChars(), node->SourceLoc); composed.VAppendFormat(msg, argptr); composed += '\n'; PrintString(PRINT_HIGH, composed); } //========================================================================== // // ZCCCompiler :: Compile // // Compile everything defined at this level. // This can be overridden to add custom content. // //========================================================================== int ZCCCompiler::Compile() { CreateClassTypes(); CreateStructTypes(); CompileAllConstants(); CompileAllFields(); InitDefaults(); InitFunctions(); return FScriptPosition::ErrorCounter; } //========================================================================== // // ZCCCompiler :: CreateStructTypes // // Creates a PStruct for every struct. // //========================================================================== void ZCCCompiler::CreateStructTypes() { for(auto s : Structs) { PTypeBase *outer; PSymbolTable *syms; s->Outer = s->OuterDef == nullptr? nullptr : s->OuterDef->CType(); if (s->Outer) { outer = s->Outer->VMType; syms = &s->Outer->VMType->Symbols; } else { outer = OutNamespace; syms = &OutNamespace->Symbols; } if (s->NodeName() == NAME__ && fileSystem.GetFileContainer(Lump) == 0) { // This is just a container for syntactic purposes. s->strct->Type = nullptr; continue; } else if (s->strct->Flags & ZCC_Native) { s->strct->Type = NewStruct(s->NodeName(), outer, true); } else { s->strct->Type = NewStruct(s->NodeName(), outer); } if (s->strct->Flags & ZCC_Version) { s->strct->Type->mVersion = s->strct->Version; } auto &sf = s->Type()->ScopeFlags; if (mVersion >= MakeVersion(2, 4, 0)) { if ((s->strct->Flags & (ZCC_UIFlag | ZCC_Play)) == (ZCC_UIFlag | ZCC_Play)) { Error(s->strct, "Struct %s has incompatible flags", s->NodeName().GetChars()); } if (outer != OutNamespace) sf = FScopeBarrier::ChangeSideInObjectFlags(sf, FScopeBarrier::SideFromObjectFlags(static_cast(outer)->ScopeFlags)); else if (s->strct->Flags & ZCC_ClearScope) Warn(s->strct, "Useless 'ClearScope' on struct %s not inside a class", s->NodeName().GetChars()); if (s->strct->Flags & ZCC_UIFlag) sf = FScopeBarrier::ChangeSideInObjectFlags(sf, FScopeBarrier::Side_UI); if (s->strct->Flags & ZCC_Play) sf = FScopeBarrier::ChangeSideInObjectFlags(sf, FScopeBarrier::Side_Play); if (s->strct->Flags & ZCC_ClearScope) sf = FScopeBarrier::ChangeSideInObjectFlags(sf, FScopeBarrier::Side_PlainData); // don't inherit the scope from the outer class } else { // old versions force 'play'. sf = FScopeBarrier::ChangeSideInObjectFlags(sf, FScopeBarrier::Side_Play); } s->strct->Symbol = Create(s->NodeName(), s->Type()); syms->AddSymbol(s->strct->Symbol); for (auto e : s->Enums) { auto etype = NewEnum(e->NodeName, s->Type()); s->Type()->Symbols.AddSymbol(Create(e->NodeName, etype)); } } } //========================================================================== // // ZCCCompiler :: CreateClassTypes // // Creates a PClass for every class so that we get access to the symbol table // These will be created with unknown size because for that we need to // process all fields first, but to do that we need the PClass and some // other info depending on the PClass. // //========================================================================== void ZCCCompiler::CreateClassTypes() { // we are going to sort the classes array so that entries are sorted in order of inheritance. auto OrigClasses = std::move(Classes); Classes.Clear(); bool donesomething = true; while (donesomething) { donesomething = false; for (unsigned i = 0; icls->ParentName; if (ParentName != nullptr && ParentName->SiblingNext == ParentName) parent = PClass::FindClass(ParentName->Id); else if (ParentName == nullptr) parent = RUNTIME_CLASS(DObject); else { // The parent is a dotted name which the type system currently does not handle. // Once it does this needs to be implemented here. auto p = ParentName; FString build; do { if (build.IsNotEmpty()) build += '.'; build += FName(p->Id).GetChars(); p = static_cast(p->SiblingNext); } while (p != ParentName); Error(c->cls, "Qualified name '%s' for base class not supported in '%s'", build.GetChars(), FName(c->NodeName()).GetChars()); parent = RUNTIME_CLASS(DObject); } if (parent != nullptr && (parent->VMType != nullptr || c->NodeName() == NAME_Object)) { // The parent exists, we may create a type for this class if (c->cls->Flags & ZCC_Native) { // If this is a native class, its own type must also already exist and not be a runtime class. auto me = PClass::FindClass(c->NodeName()); if (me == nullptr) { Error(c->cls, "Unknown native class %s", c->NodeName().GetChars()); // Create a placeholder so that the compiler can continue looking for errors. me = parent->FindClassTentative(c->NodeName()); } else if (me->bRuntimeClass) { Error(c->cls, "%s is not a native class", c->NodeName().GetChars()); } else { DPrintf(DMSG_SPAMMY, "Registered %s as native with parent %s\n", me->TypeName.GetChars(), parent->TypeName.GetChars()); } c->cls->Type = NewClassType(me); me->SourceLumpName = *c->cls->SourceName; } else { // We will never get here if the name is a duplicate, so we can just do the assignment. try { if (parent->VMType->mVersion > mVersion) { Error(c->cls, "Parent class %s of %s not accessible to ZScript version %d.%d.%d", parent->TypeName.GetChars(), c->NodeName().GetChars(), mVersion.major, mVersion.minor, mVersion.revision); } auto newclass = parent->CreateDerivedClass(c->NodeName(), TentativeClass); if (newclass == nullptr) { Error(c->cls, "Class name %s already exists", c->NodeName().GetChars()); } else { c->cls->Type = NewClassType(newclass); DPrintf(DMSG_SPAMMY, "Created class %s with parent %s\n", c->Type()->TypeName.GetChars(), c->ClassType()->ParentClass->TypeName.GetChars()); } } catch (CRecoverableError &err) { Error(c->cls, "%s", err.GetMessage()); c->cls->Type = nullptr; } } if (c->Type() == nullptr) { // create a placeholder so that the compiler can continue looking for errors. c->cls->Type = NewClassType(parent->FindClassTentative(c->NodeName())); } if (c->cls->Flags & ZCC_Abstract) c->ClassType()->bAbstract = true; if (c->cls->Flags & ZCC_Version) { c->Type()->mVersion = c->cls->Version; } // if (mVersion >= MakeVersion(2, 4, 0)) { static int incompatible[] = { ZCC_UIFlag, ZCC_Play, ZCC_ClearScope }; int incompatiblecnt = 0; for (size_t k = 0; k < countof(incompatible); k++) if (incompatible[k] & c->cls->Flags) incompatiblecnt++; if (incompatiblecnt > 1) { Error(c->cls, "Class %s has incompatible flags", c->NodeName().GetChars()); } if (c->cls->Flags & ZCC_UIFlag) c->Type()->ScopeFlags = EScopeFlags((c->Type()->ScopeFlags&~Scope_Play) | Scope_UI); if (c->cls->Flags & ZCC_Play) c->Type()->ScopeFlags = EScopeFlags((c->Type()->ScopeFlags&~Scope_UI) | Scope_Play); if (parent->VMType->ScopeFlags & (Scope_UI | Scope_Play)) // parent is either ui or play { if (c->cls->Flags & (ZCC_UIFlag | ZCC_Play)) { Error(c->cls, "Can't change class scope in class %s", c->NodeName().GetChars()); } c->Type()->ScopeFlags = FScopeBarrier::ChangeSideInObjectFlags(c->Type()->ScopeFlags, FScopeBarrier::SideFromObjectFlags(parent->VMType->ScopeFlags)); } } else { c->Type()->ScopeFlags = FScopeBarrier::ChangeSideInObjectFlags(c->Type()->ScopeFlags, FScopeBarrier::Side_Play); } c->cls->Symbol = Create(c->NodeName(), c->Type()); OutNamespace->Symbols.AddSymbol(c->cls->Symbol); Classes.Push(c); OrigClasses.Delete(i--); donesomething = true; } else if (c->cls->ParentName != nullptr) { // No base class found. Now check if something in the unprocessed classes matches. // If not, print an error. If something is found let's retry again in the next iteration. bool found = false; for (auto d : OrigClasses) { if (d->NodeName() == c->cls->ParentName->Id) { found = true; break; } } if (!found) { Error(c->cls, "Class %s has unknown base class %s", c->NodeName().GetChars(), FName(c->cls->ParentName->Id).GetChars()); // create a placeholder so that the compiler can continue looking for errors. c->cls->Type = NewClassType(RUNTIME_CLASS(DObject)->FindClassTentative(c->NodeName())); c->cls->Symbol = Create(c->NodeName(), c->Type()); OutNamespace->Symbols.AddSymbol(c->cls->Symbol); Classes.Push(c); OrigClasses.Delete(i--); donesomething = true; } } } } // What's left refers to some other class in the list but could not be resolved. // This normally means a circular reference. for (auto c : OrigClasses) { Error(c->cls, "Class %s has circular inheritance", FName(c->NodeName()).GetChars()); c->cls->Type = NewClassType(RUNTIME_CLASS(DObject)->FindClassTentative(c->NodeName())); c->cls->Symbol = Create(c->NodeName(), c->Type()); OutNamespace->Symbols.AddSymbol(c->cls->Symbol); Classes.Push(c); } // Last but not least: Now that all classes have been created, we can create the symbols for the internal enums and link the treenode symbol tables. for (auto cd : Classes) { for (auto e : cd->Enums) { auto etype = NewEnum(e->NodeName, cd->Type()); cd->Type()->Symbols.AddSymbol(Create(e->NodeName, etype)); } // Link the tree node tables. We only can do this after we know the class relations. for (auto cc : Classes) { if (cc->ClassType() == cd->ClassType()->ParentClass) { cd->TreeNodes.SetParentTable(&cc->TreeNodes); break; } } } } //========================================================================== // // ZCCCompiler :: AddConstants // // Helper for CompileAllConstants // //========================================================================== void ZCCCompiler::CopyConstants(TArray &dest, TArray &Constants, PContainerType *cls, PSymbolTable *ot) { for (auto c : Constants) { dest.Push({ c, cls, ot }); } } //========================================================================== // // ZCCCompiler :: CompileAllConstants // // Make symbols from every constant defined at all levels. // Since constants may only depend on other constants this can be done // without any more involved processing of the AST as a first step. // //========================================================================== void ZCCCompiler::CompileAllConstants() { // put all constants in one list to make resolving this easier. TArray constantwork; CopyConstants(constantwork, Constants, nullptr, &OutNamespace->Symbols); for (auto c : Classes) { CopyConstants(constantwork, c->Constants, c->Type(), &c->Type()->Symbols); } for (auto s : Structs) { if (s->Type() != nullptr) CopyConstants(constantwork, s->Constants, s->Type(), &s->Type()->Symbols); } // Before starting to resolve the list, let's create symbols for all already resolved ones first (i.e. all literal constants), to reduce work. for (unsigned i = 0; iValue->NodeType == AST_ExprConstant) { AddConstant(constantwork[i]); // Remove the constant from the list constantwork.Delete(i); i--; } } bool donesomething = true; // Now go through this list until no more constants can be resolved. The remaining ones will be non-constant values. while (donesomething && constantwork.Size() > 0) { donesomething = false; for (unsigned i = 0; i < constantwork.Size(); i++) { if (CompileConstant(&constantwork[i])) { AddConstant(constantwork[i]); // Remove the constant from the list constantwork.Delete(i); i--; donesomething = true; } } } for (unsigned i = 0; i < constantwork.Size(); i++) { Error(constantwork[i].node, "%s is not a constant", FName(constantwork[i].node->NodeName).GetChars()); } for (auto s : Structs) { CompileArrays(s); } for (auto c : Classes) { CompileArrays(c); } } //========================================================================== // // ZCCCompiler :: AddConstant // // Adds a constant to its assigned symbol table // //========================================================================== void ZCCCompiler::AddConstant(ZCC_ConstantWork &constant) { auto def = constant.node; auto val = def->Value; ExpVal &c = constant.constval; // This is for literal constants. if (val->NodeType == AST_ExprConstant) { ZCC_ExprConstant *cval = static_cast(val); if (cval->Type == TypeString) { def->Symbol = Create(def->NodeName, *(cval->StringVal)); } else if (cval->Type->isInt()) { // How do we get an Enum type in here without screwing everything up??? //auto type = def->Type != nullptr ? def->Type : cval->Type; def->Symbol = Create(def->NodeName, cval->Type, cval->IntVal); } else if (cval->Type->isFloat()) { if (def->Type != nullptr) { Error(def, "Enum members must be integer values"); } def->Symbol = Create(def->NodeName, cval->Type, cval->DoubleVal); } else { Error(def->Value, "Bad type for constant definiton"); def->Symbol = nullptr; } } else { if (c.Type == TypeString) { def->Symbol = Create(def->NodeName, c.GetString()); } else if (c.Type->isInt()) { // How do we get an Enum type in here without screwing everything up??? //auto type = def->Type != nullptr ? def->Type : cval->Type; def->Symbol = Create(def->NodeName, c.Type, c.GetInt()); } else if (c.Type->isFloat()) { if (def->Type != nullptr) { Error(def, "Enum members must be integer values"); } def->Symbol = Create(def->NodeName, c.Type, c.GetFloat()); } else { Error(def->Value, "Bad type for constant definiton"); def->Symbol = nullptr; } } if (def->Symbol == nullptr) { // Create a dummy constant so we don't make any undefined value warnings. def->Symbol = Create(def->NodeName, TypeError, 0); } constant.Outputtable->ReplaceSymbol(def->Symbol); } //========================================================================== // // ZCCCompiler :: CompileConstant // // For every constant definition, evaluate its value (which should result // in a constant), and create a symbol for it. // //========================================================================== bool ZCCCompiler::CompileConstant(ZCC_ConstantWork *work) { FCompileContext ctx(OutNamespace, work->cls, false, mVersion); FxExpression *exp = ConvertNode(work->node->Value); try { FScriptPosition::errorout = true; exp = exp->Resolve(ctx); if (exp == nullptr) return false; FScriptPosition::errorout = false; if (!exp->isConstant()) { delete exp; return false; } work->constval = static_cast(exp)->GetValue(); delete exp; return true; } catch (...) { // eat the reported error and treat this as a temorary failure. All unresolved contants will be reported at the end. FScriptPosition::errorout = false; return false; } } void ZCCCompiler::CompileArrays(ZCC_StructWork *work) { for(auto sas : work->Arrays) { PType *ztype = DetermineType(work->Type(), sas, sas->Id, sas->Type, false, true); PType *ctype = ztype; FArgumentList values; // Don't use narrow typea for casting. if (ctype->isInt()) ctype = static_cast(ztype)->Unsigned ? TypeUInt32 : TypeSInt32; else if (ctype == TypeFloat32) ctype = TypeFloat64; ConvertNodeList(values, sas->Values); bool fail = false; FCompileContext ctx(OutNamespace, work->Type(), false, mVersion); char *destmem = (char *)ClassDataAllocator.Alloc(values.Size() * ztype->Align); memset(destmem, 0, values.Size() * ztype->Align); char *copyp = destmem; for (unsigned i = 0; i < values.Size(); i++) { values[i] = new FxTypeCast(values[i], ctype, false); values[i] = values[i]->Resolve(ctx); if (values[i] == nullptr) fail = true; else if (!values[i]->isConstant()) { Error(sas, "Initializer must be constant"); fail = true; } else { ExpVal val = static_cast(values[i])->GetValue(); switch (ztype->GetRegType()) { default: // should never happen Error(sas, "Non-integral type in constant array"); return; case REGT_INT: ztype->SetValue(copyp, val.GetInt()); break; case REGT_FLOAT: ztype->SetValue(copyp, val.GetFloat()); break; case REGT_POINTER: *(void**)copyp = val.GetPointer(); break; case REGT_STRING: ::new(copyp) FString(val.GetString()); break; } copyp += ztype->Align; } } work->Type()->Symbols.AddSymbol(Create(sas->Id, NewArray(ztype, values.Size()), VARF_Static | VARF_ReadOnly, (size_t)destmem)); } } //========================================================================== // // ZCCCompiler :: NodeFromSymbol // //========================================================================== ZCC_Expression *ZCCCompiler::NodeFromSymbol(PSymbol *sym, ZCC_Expression *source, PSymbolTable *table) { assert(sym != nullptr); if (sym->IsKindOf(RUNTIME_CLASS(PSymbolConst))) { return NodeFromSymbolConst(static_cast(sym), source); } else if (sym->IsKindOf(RUNTIME_CLASS(PSymbolType))) { return NodeFromSymbolType(static_cast(sym), source); } return NULL; } //========================================================================== // // ZCCCompiler :: NodeFromSymbolConst // // Returns a new AST constant node with the symbol's content. // //========================================================================== ZCC_ExprConstant *ZCCCompiler::NodeFromSymbolConst(PSymbolConst *sym, ZCC_Expression *idnode) { ZCC_ExprConstant *val = static_cast(AST.InitNode(sizeof(*val), AST_ExprConstant, idnode)); val->Operation = PEX_ConstValue; if (sym == NULL) { val->Type = TypeError; val->IntVal = 0; } else if (sym->IsKindOf(RUNTIME_CLASS(PSymbolConstString))) { val->StringVal = AST.Strings.Alloc(static_cast(sym)->Str); val->Type = TypeString; } else { val->Type = sym->ValueType; if (val->Type != TypeError) { assert(sym->IsKindOf(RUNTIME_CLASS(PSymbolConstNumeric))); if (sym->ValueType->isIntCompatible()) { val->IntVal = static_cast(sym)->Value; } else { assert(sym->ValueType->isFloat()); val->DoubleVal = static_cast(sym)->Float; } } } return val; } //========================================================================== // // ZCCCompiler :: NodeFromSymbolType // // Returns a new AST type ref node with the symbol's content. // //========================================================================== ZCC_ExprTypeRef *ZCCCompiler::NodeFromSymbolType(PSymbolType *sym, ZCC_Expression *idnode) { ZCC_ExprTypeRef *ref = static_cast(AST.InitNode(sizeof(*ref), AST_ExprTypeRef, idnode)); ref->Operation = PEX_TypeRef; ref->RefType = sym->Type; ref->Type = NewClassPointer(RUNTIME_CLASS(DObject)); return ref; } //========================================================================== // // ZCCCompiler :: CompileAllFields // // builds the internal structure of all classes and structs // //========================================================================== void ZCCCompiler::CompileAllFields() { // Create copies of the arrays which can be altered auto Classes = this->Classes; auto Structs = OrderStructs(); TMap HasNativeChildren; // first step: Look for native classes with native children. // These may not have any variables added to them because it'd clash with the native definitions. for (unsigned i = 0; i < Classes.Size(); i++) { auto c = Classes[i]; if (c->Type()->Size != TentativeClass && c->Fields.Size() > 0) { // We need to search the global class table here because not all children may have a scripted definition attached. for (auto ac : PClass::AllClasses) { if (ac->ParentClass != nullptr && ac->ParentClass->VMType == c->Type() && ac->Size != TentativeClass) { // Only set a marker here, so that we can print a better message when the actual fields get added. HasNativeChildren.Insert(c->Type()->TypeName, true); break; } } } } bool donesomething = true; while (donesomething && (Structs.Size() > 0 || Classes.Size() > 0)) { donesomething = false; for (unsigned i = 0; i < Structs.Size(); i++) { if (CompileFields(Structs[i]->Type(), Structs[i]->Fields, Structs[i]->Outer, Structs[i]->Type() == 0? GlobalTreeNodes : &Structs[i]->TreeNodes, true)) { // Remove from the list if all fields got compiled. Structs.Delete(i--); donesomething = true; } } for (unsigned i = 0; i < Classes.Size(); i++) { auto type = Classes[i]->ClassType(); if (type->Size == TentativeClass) { if (type->ParentClass->Size == TentativeClass) { // we do not know the parent class's size yet, so skip this class for now. continue; } else { // Inherit the size of the parent class type->Size = Classes[i]->ClassType()->ParentClass->Size; } } if (!PrepareMetaData(type)) { if (Classes[i]->ClassType()->ParentClass) type->MetaSize = Classes[i]->ClassType()->ParentClass->MetaSize; else type->MetaSize = 0; } if (CompileFields(type->VMType, Classes[i]->Fields, nullptr, &Classes[i]->TreeNodes, false, !!HasNativeChildren.CheckKey(type->TypeName))) { // Remove from the list if all fields got compiled. Classes.Delete(i--); donesomething = true; } } } // This really should never happen, but if it does, let's better print an error. for (auto s : Structs) { Error(s->strct, "Unable to resolve all fields for struct %s", FName(s->NodeName()).GetChars()); } for (auto s : Classes) { Error(s->cls, "Unable to resolve all fields for class %s", FName(s->NodeName()).GetChars()); } } //========================================================================== // // ZCCCompiler :: CompileFields // // builds the internal structure of a single class or struct // //========================================================================== bool ZCCCompiler::CompileFields(PContainerType *type, TArray &Fields, PClass *Outer, PSymbolTable *TreeNodes, bool forstruct, bool hasnativechildren) { while (Fields.Size() > 0) { auto field = Fields[0]; FieldDesc *fd = nullptr; PType *fieldtype = DetermineType(type, field, field->Names->Name, field->Type, true, true); // For structs only allow 'deprecated', for classes exclude function qualifiers. int notallowed = forstruct? ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Meta | ZCC_Extension | ZCC_VirtualScope | ZCC_ClearScope : ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Extension | ZCC_VirtualScope | ZCC_ClearScope; // Some internal fields need to be set to clearscope. if (fileSystem.GetFileContainer(Lump) == 0) notallowed &= ~ZCC_ClearScope; if (field->Flags & notallowed) { Error(field, "Invalid qualifiers for %s (%s not allowed)", FName(field->Names->Name).GetChars(), FlagsToString(field->Flags & notallowed).GetChars()); field->Flags &= notallowed; } uint32_t varflags = 0; // These map directly to implementation flags. if (field->Flags & ZCC_Private) varflags |= VARF_Private; if (field->Flags & ZCC_Protected) varflags |= VARF_Protected; if (field->Flags & ZCC_Deprecated) varflags |= VARF_Deprecated; if (field->Flags & ZCC_ReadOnly) varflags |= VARF_ReadOnly; if (field->Flags & ZCC_Internal) varflags |= VARF_InternalAccess; if (field->Flags & ZCC_Transient) varflags |= VARF_Transient; if (mVersion >= MakeVersion(2, 4, 0)) { if (type != nullptr) { if (type->ScopeFlags & Scope_UI) varflags |= VARF_UI; if (type->ScopeFlags & Scope_Play) varflags |= VARF_Play; } if (field->Flags & ZCC_UIFlag) varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_UI); if (field->Flags & ZCC_Play) varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Play); if (field->Flags & ZCC_ClearScope) varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_PlainData); } else { varflags |= VARF_Play; } if (field->Flags & ZCC_Native) { varflags |= VARF_Native | VARF_Transient; } static int excludescope[] = { ZCC_UIFlag, ZCC_Play, ZCC_ClearScope }; int excludeflags = 0; int fc = 0; for (size_t i = 0; i < countof(excludescope); i++) { if (field->Flags & excludescope[i]) { fc++; excludeflags |= excludescope[i]; } } if (fc > 1) { Error(field, "Invalid combination of scope qualifiers %s on field %s", FlagsToString(excludeflags).GetChars(), FName(field->Names->Name).GetChars()); varflags &= ~(VARF_UI | VARF_Play); // make plain data } if (field->Flags & ZCC_Meta) { varflags |= VARF_Meta | VARF_Static | VARF_ReadOnly; // metadata implies readonly } if (field->Type->ArraySize != nullptr) { bool nosize; fieldtype = ResolveArraySize(fieldtype, field->Type->ArraySize, type, &nosize); if (nosize) { Error(field, "Must specify array size"); } } auto name = field->Names; do { if ((fieldtype->Size == 0 || !fieldtype->SizeKnown) && !(varflags & VARF_Native)) // Size not known yet. { if (type != nullptr) { type->SizeKnown = false; } return false; } if (AddTreeNode(name->Name, name, TreeNodes, !forstruct)) { auto thisfieldtype = fieldtype; if (name->ArraySize != nullptr) { bool nosize; thisfieldtype = ResolveArraySize(thisfieldtype, name->ArraySize, type, &nosize); if (nosize) { Error(field, "Must specify array size"); } } PField *f = nullptr; if (varflags & VARF_Native) { if (varflags & VARF_Meta) { Error(field, "Native meta variable %s not allowed", FName(name->Name).GetChars()); } else { fd = FindField(type, FName(name->Name).GetChars()); if (fd == nullptr) { Error(field, "The member variable '%s.%s' has not been exported from the executable.", type == nullptr? "" : type->TypeName.GetChars(), FName(name->Name).GetChars()); } // For native structs a size check cannot be done because they normally have no size. But for a native reference they are still fine. else if (thisfieldtype->Size != ~0u && fd->FieldSize != ~0u && thisfieldtype->Size != fd->FieldSize && fd->BitValue == 0 && (!thisfieldtype->isStruct() || !static_cast(thisfieldtype)->isNative)) { Error(field, "The member variable '%s.%s' has mismatching sizes in internal and external declaration. (Internal = %d, External = %d)", type == nullptr ? "" : type->TypeName.GetChars(), FName(name->Name).GetChars(), fd->FieldSize, thisfieldtype->Size); } // Q: Should we check alignment, too? A mismatch may be an indicator for bad assumptions. else if (type != nullptr) { // for bit fields the type must point to the source variable. if (fd->BitValue != 0) thisfieldtype = fd->FieldSize == 1 ? TypeUInt8 : fd->FieldSize == 2 ? TypeUInt16 : TypeUInt32; f = type->AddNativeField(name->Name, thisfieldtype, fd->FieldOffset, varflags, fd->BitValue); } else { // This is a global variable. if (fd->BitValue != 0) thisfieldtype = fd->FieldSize == 1 ? TypeUInt8 : fd->FieldSize == 2 ? TypeUInt16 : TypeUInt32; f = Create(name->Name, thisfieldtype, varflags | VARF_Native | VARF_Static, fd->FieldOffset, fd->BitValue); if (OutNamespace->Symbols.AddSymbol(f) == nullptr) { // name is already in use if (type != nullptr) { type->SizeKnown = false; } f->Destroy(); return false; } } } } else if (hasnativechildren && !(varflags & VARF_Meta)) { Error(field, "Cannot add field %s to %s. %s has native children which means it size may not change", FName(name->Name).GetChars(), type->TypeName.GetChars(), type->TypeName.GetChars()); } else if (type != nullptr) { f = type->AddField(name->Name, thisfieldtype, varflags); } else { Error(field, "Cannot declare non-native global variables. Tried to declare %s", FName(name->Name).GetChars()); } if ((field->Flags & (ZCC_Version | ZCC_Deprecated)) && f != nullptr) { f->mVersion = field->Version; if (field->DeprecationMessage != nullptr) { f->DeprecationMessage = *field->DeprecationMessage; } } } name = static_cast(name->SiblingNext); } while (name != field->Names); Fields.Delete(0); } if (type != nullptr) { type->SizeKnown = Fields.Size() == 0; } return Fields.Size() == 0; } //========================================================================== // // ZCCCompiler :: OrderStructs // // Order the Structs array so that the least-dependant structs come first // //========================================================================== TArray ZCCCompiler::OrderStructs() { TArray new_order; for (auto struct_def : Structs) { if (std::find(new_order.begin(), new_order.end(), struct_def) != new_order.end()) { continue; } AddStruct(new_order, struct_def); } return new_order; } //========================================================================== // // ZCCCompiler :: AddStruct // // Adds a struct to the Structs array, preceded by all its dependant structs // //========================================================================== void ZCCCompiler::AddStruct(TArray &new_order, ZCC_StructWork *my_def) { PStruct *my_type = static_cast(my_def->Type()); if (my_type) { if (my_type->isOrdered) { return; } my_type->isOrdered = true; } // Find all struct fields and add them before this one for (const auto field : my_def->Fields) { PType *fieldtype = DetermineType(my_type, field, field->Names->Name, field->Type, true, true); if (fieldtype->isStruct() && !static_cast(fieldtype)->isOrdered) { AddStruct(new_order, StructTypeToWork(static_cast(fieldtype))); } } new_order.Push(my_def); } //========================================================================== // // ZCCCompiler :: StructTypeToWork // // Find the ZCC_StructWork that corresponds to a PStruct // //========================================================================== ZCC_StructWork *ZCCCompiler::StructTypeToWork(const PStruct *type) const { assert(type->isStruct()); for (auto &def : Structs) { if (def->Type() == type) { return def; } } assert(false && "Struct not found"); return nullptr; } //========================================================================== // // ZCCCompiler :: FieldFlagsToString // // creates a string for a field's flags // //========================================================================== FString ZCCCompiler::FlagsToString(uint32_t flags) { const char *flagnames[] = { "native", "static", "private", "protected", "latent", "final", "meta", "action", "deprecated", "readonly", "const", "abstract", "extend", "virtual", "override", "transient", "vararg", "ui", "play", "clearscope", "virtualscope" }; FString build; for (size_t i = 0; i < countof(flagnames); i++) { if (flags & (1 << i)) { if (build.IsNotEmpty()) build += ", "; build += flagnames[i]; } } return build; } //========================================================================== // // ZCCCompiler :: DetermineType // // retrieves the type for this field, for arrays the type of a single entry. // //========================================================================== PType *ZCCCompiler::DetermineType(PType *outertype, ZCC_TreeNode *field, FName name, ZCC_Type *ztype, bool allowarraytypes, bool formember) { PType *retval = TypeError; if (!allowarraytypes && ztype->ArraySize != nullptr) { Error(field, "%s: Array type not allowed", name.GetChars()); return TypeError; } switch (ztype->NodeType) { case AST_BasicType: { auto btype = static_cast(ztype); switch (btype->Type) { case ZCC_SInt8: retval = formember? TypeSInt8 : (PType*)TypeError; break; case ZCC_UInt8: retval = formember ? TypeUInt8 : (PType*)TypeError; break; case ZCC_SInt16: retval = formember ? TypeSInt16 : (PType*)TypeError; break; case ZCC_UInt16: retval = formember ? TypeUInt16 : (PType*)TypeError; break; case ZCC_SInt32: case ZCC_IntAuto: // todo: for enums, autoselect appropriately sized int retval = TypeSInt32; break; case ZCC_UInt32: retval = TypeUInt32; break; case ZCC_Bool: retval = TypeBool; break; case ZCC_FloatAuto: retval = formember ? TypeFloat32 : TypeFloat64; break; case ZCC_Float64: retval = TypeFloat64; break; case ZCC_String: retval = TypeString; break; case ZCC_Name: retval = TypeName; break; case ZCC_Vector2: retval = TypeVector2; break; case ZCC_Vector3: retval = TypeVector3; break; case ZCC_Vector4: retval = TypeVector4; break; case ZCC_State: retval = TypeState; break; case ZCC_Color: retval = TypeColor; break; case ZCC_Sound: retval = TypeSound; break; case ZCC_Let: retval = TypeAuto; break; case ZCC_NativeType: // Creating an instance of a native struct is only allowed for internal definitions of native variables. if (fileSystem.GetFileContainer(Lump) != 0 || !formember) { Error(field, "%s: @ not allowed for user scripts", name.GetChars()); } retval = ResolveUserType(btype, btype->UserType, outertype? &outertype->Symbols : nullptr, true); break; case ZCC_UserType: // statelabel et.al. are not tokens - there really is no need to, it works just as well as an identifier. Maybe the same should be done for some other types, too? switch (btype->UserType->Id) { case NAME_Voidptr: retval = TypeVoidPtr; break; case NAME_StateLabel: retval = TypeStateLabel; break; case NAME_SpriteID: retval = TypeSpriteID; break; case NAME_TextureID: retval = TypeTextureID; break; default: retval = ResolveUserType(btype, btype->UserType, outertype ? &outertype->Symbols : nullptr, false); break; } break; default: break; } break; } case AST_MapType: { if(AST.ParseVersion < MakeVersion(4, 10, 0)) { Error(field, "Map not accessible to ZScript version %d.%d.%d", AST.ParseVersion.major, AST.ParseVersion.minor, AST.ParseVersion.revision); break; } // Todo: Decide what we allow here and if it makes sense to allow more complex constructs. auto mtype = static_cast(ztype); auto keytype = DetermineType(outertype, field, name, mtype->KeyType, false, false); auto valuetype = DetermineType(outertype, field, name, mtype->ValueType, false, false); if (keytype->GetRegType() == REGT_INT) { if (keytype->Size != 4) { Error(field, "Map<%s , ...> not implemented yet", keytype->DescriptiveName()); break; } } else if (keytype->GetRegType() != REGT_STRING) { Error(field, "Map<%s , ...> not implemented yet", keytype->DescriptiveName()); break; } switch(valuetype->GetRegType()) { case REGT_FLOAT: case REGT_INT: case REGT_STRING: case REGT_POINTER: if (valuetype->GetRegCount() > 1) { Error(field, "%s : Base type for map value types must be integral, but got %s", name.GetChars(), valuetype->DescriptiveName()); break; } retval = NewMap(keytype, valuetype); break; default: Error(field, "%s: Base type for map value types must be integral, but got %s", name.GetChars(), valuetype->DescriptiveName()); } break; } case AST_MapIteratorType: { if(AST.ParseVersion < MakeVersion(4, 10, 0)) { Error(field, "MapIterator not accessible to ZScript version %d.%d.%d", AST.ParseVersion.major, AST.ParseVersion.minor, AST.ParseVersion.revision); break; } // Todo: Decide what we allow here and if it makes sense to allow more complex constructs. auto mtype = static_cast(ztype); auto keytype = DetermineType(outertype, field, name, mtype->KeyType, false, false); auto valuetype = DetermineType(outertype, field, name, mtype->ValueType, false, false); if (keytype->GetRegType() == REGT_INT) { if (keytype->Size != 4) { Error(field, "MapIterator<%s , ...> not implemented yet", keytype->DescriptiveName()); } } else if (keytype->GetRegType() != REGT_STRING) { Error(field, "MapIterator<%s , ...> not implemented yet", keytype->DescriptiveName()); } switch(valuetype->GetRegType()) { case REGT_FLOAT: case REGT_INT: case REGT_STRING: case REGT_POINTER: if (valuetype->GetRegCount() > 1) { Error(field, "%s : Base type for map value types must be integral, but got %s", name.GetChars(), valuetype->DescriptiveName()); break; } retval = NewMapIterator(keytype, valuetype); break; default: Error(field, "%s: Base type for map value types must be integral, but got %s", name.GetChars(), valuetype->DescriptiveName()); } break; } case AST_DynArrayType: { auto atype = static_cast(ztype); auto ftype = DetermineType(outertype, field, name, atype->ElementType, false, true); if (ftype->GetRegType() == REGT_NIL || ftype->GetRegCount() > 1) { if (field->NodeType == AST_VarDeclarator && (static_cast(field)->Flags & ZCC_Native) && fileSystem.GetFileContainer(Lump) == 0) { // the internal definitions may declare native arrays to complex types. // As long as they can be mapped to a static array type the VM can handle them, in a limited but sufficient fashion. retval = NewPointer(NewStaticArray(ftype), false); retval->Size = ~0u; // don't check for a size match, it's likely to fail anyway. retval->Align = ~0u; } else { Error(field, "%s: Base type for dynamic array types must be integral, but got %s", name.GetChars(), ftype->DescriptiveName()); } } else { retval = NewDynArray(ftype); } break; } case AST_ClassType: { auto ctype = static_cast(ztype); if (ctype->Restriction == nullptr) { retval = NewClassPointer(RUNTIME_CLASS(DObject)); } else { // This doesn't check the class list directly but the current symbol table to ensure that // this does not reference a type that got shadowed by a more local definition. // We first look in the current class and its parents, and then in the current namespace and its parents. auto sym = outertype ? outertype->Symbols.FindSymbol(ctype->Restriction->Id, true) : nullptr; if (sym == nullptr) sym = OutNamespace->Symbols.FindSymbol(ctype->Restriction->Id, true); if (sym == nullptr) { // A symbol with a given name cannot be reached from this definition point, so // even if a class with the given name exists, it is not accessible. Error(field, "%s: Unknown identifier", FName(ctype->Restriction->Id).GetChars()); return TypeError; } auto typesym = dyn_cast(sym); if (typesym == nullptr || !typesym->Type->isClass()) { Error(field, "%s does not represent a class type", FName(ctype->Restriction->Id).GetChars()); return TypeError; } if (typesym->Type->mVersion > mVersion) { Error(field, "Class %s not accessible to ZScript version %d.%d.%d", FName(ctype->Restriction->Id).GetChars(), mVersion.major, mVersion.minor, mVersion.revision); return TypeError; } retval = NewClassPointer(static_cast(typesym->Type)->Descriptor); } break; } default: break; } if (retval != TypeError && retval->MemberOnly && !formember) { Error(field, "Invalid type %s", retval->DescriptiveName()); return TypeError; } return retval; } //========================================================================== // // ZCCCompiler :: ResolveUserType // //========================================================================== /** * Resolves a user type and returns a matching PType. * * @param type The tree node with the identifiers to look for. * @param type The current identifier being looked for. This must be in type's UserType list. * @param symt The symbol table to search in. If id is the first identifier and not found in symt, then OutNamespace will also be searched. * @param nativetype Distinguishes between searching for a native type or a user type. * @returns the PType found for this user type */ PType *ZCCCompiler::ResolveUserType(ZCC_BasicType *type, ZCC_Identifier *id, PSymbolTable *symt, bool nativetype) { // Check the symbol table for the identifier. PSymbol *sym = nullptr; // We first look in the current class and its parents, and then in the current namespace and its parents. if (symt != nullptr) sym = symt->FindSymbol(id->Id, true); if (sym == nullptr && type->UserType == id) sym = OutNamespace->Symbols.FindSymbol(id->Id, true); if (sym != nullptr && sym->IsKindOf(RUNTIME_CLASS(PSymbolType))) { auto ptype = static_cast(sym)->Type; if (ptype->mVersion > mVersion) { Error(type, "Type %s not accessible to ZScript version %d.%d.%d", FName(type->UserType->Id).GetChars(), mVersion.major, mVersion.minor, mVersion.revision); return TypeError; } if (id->SiblingNext != type->UserType) { assert(id->SiblingNext->NodeType == AST_Identifier); ptype = ResolveUserType( type, static_cast(id->SiblingNext), &ptype->Symbols, nativetype ); if (ptype == TypeError) { return ptype; } } if (ptype->isEnum()) { if (!nativetype) return TypeSInt32; // hack this to an integer until we can resolve the enum mess. } else if (ptype->isClass()) // classes cannot be instantiated at all, they always get used as references. { return NewPointer(ptype, type->isconst); } else if (ptype->isStruct() && static_cast(ptype)->isNative) // native structs and classes cannot be instantiated, they always get used as reference. { if (!nativetype) return NewPointer(ptype, type->isconst); return ptype; // instantiation of native structs. Only for internal use. } if (!nativetype) return ptype; } Error(type, "Unable to resolve %s%s as a type.", nativetype? "@" : "", UserTypeName(type).GetChars()); return TypeError; } //========================================================================== // // ZCCCompiler :: UserTypeName STATIC // // Returns the full name for a UserType node. // //========================================================================== FString ZCCCompiler::UserTypeName(ZCC_BasicType *type) { FString out; ZCC_Identifier *id = type->UserType; do { assert(id->NodeType == AST_Identifier); if (out.Len() > 0) { out += '.'; } out += FName(id->Id).GetChars(); } while ((id = static_cast(id->SiblingNext)) != type->UserType); return out; } //========================================================================== // // ZCCCompiler :: ResolveArraySize // // resolves the array size and returns a matching type. // //========================================================================== PType *ZCCCompiler::ResolveArraySize(PType *baseType, ZCC_Expression *arraysize, PContainerType *cls, bool *nosize) { TArray indices; // Simplify is too broken to resolve this inside the ring list so unravel the list into an array before starting to simplify its components. auto node = arraysize; do { indices.Push(node); node = static_cast(node->SiblingNext); } while (node != arraysize); if (indices.Size() == 1 && indices [0]->Operation == PEX_Nil) { *nosize = true; return baseType; } if (mVersion >= MakeVersion(3, 7, 2)) { TArray fixedIndices; for (auto index : indices) { fixedIndices.Insert (0, index); } indices = std::move(fixedIndices); } FCompileContext ctx(OutNamespace, cls, false, mVersion); for (auto index : indices) { // There is no float->int casting here. FxExpression *ex = ConvertNode(index); ex = ex->Resolve(ctx); if (ex == nullptr) return TypeError; if (!ex->isConstant() || !ex->ValueType->isInt()) { Error(arraysize, "Array index must be an integer constant"); return TypeError; } int size = static_cast(ex)->GetValue().GetInt(); if (size < 1) { Error(arraysize, "Array size must be positive"); return TypeError; } baseType = NewArray(baseType, size); } *nosize = false; return baseType; } //========================================================================== // // SetImplicitArgs // // Adds the parameters implied by the function flags. // //========================================================================== void ZCCCompiler::SetImplicitArgs(TArray* args, TArray* argflags, TArray* argnames, PContainerType* cls, uint32_t funcflags, int useflags) { // Must be called before adding any other arguments. assert(args == nullptr || args->Size() == 0); assert(argflags == nullptr || argflags->Size() == 0); if (funcflags & VARF_Method) { // implied self pointer if (args != nullptr) args->Push(NewPointer(cls, !!(funcflags & VARF_ReadOnly))); if (argflags != nullptr) argflags->Push(VARF_Implicit | VARF_ReadOnly); if (argnames != nullptr) argnames->Push(NAME_self); } } void ZCCCompiler::InitDefaults() { for (auto c : Classes) { if (c->ClassType()->ParentClass) { auto ti = c->ClassType(); ti->InitializeDefaults(); } } } //========================================================================== // // // //========================================================================== void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool forclass) { TArray rets(1); TArray args; TArray argflags; TArray argdefaults; TArray argnames; rets.Clear(); args.Clear(); argflags.Clear(); bool hasdefault = false; // For the time being, let's not allow overloading. This may be reconsidered later but really just adds an unnecessary amount of complexity here. if (AddTreeNode(f->Name, f, &c->TreeNodes, false)) { auto t = f->Type; if (t != nullptr) { do { auto type = DetermineType(c->Type(), f, f->Name, t, false, false); if (type->isContainer() && type != TypeVector2 && type != TypeVector3 && type != TypeVector4 && type != TypeQuaternion && type != TypeFVector2 && type != TypeFVector3 && type != TypeFVector4 && type != TypeFQuaternion) { // structs and classes only get passed by pointer. type = NewPointer(type); } else if (type->isDynArray()) { Error(f, "The return type of a function cannot be a dynamic array"); break; } else if (type == TypeFVector2) { type = TypeVector2; } else if (type == TypeFVector3) { type = TypeVector3; } else if (type == TypeFVector4) { type = TypeVector4; } else if (type == TypeFQuaternion) { type = TypeQuaternion; } // TBD: disallow certain types? For now, let everything pass that isn't an array. rets.Push(type); t = static_cast(t->SiblingNext); } while (t != f->Type); } int notallowed = ZCC_Latent | ZCC_Meta | ZCC_ReadOnly | ZCC_Internal; if (f->Flags & notallowed) { Error(f, "Invalid qualifiers for %s (%s not allowed)", FName(f->Name).GetChars(), FlagsToString(f->Flags & notallowed).GetChars()); f->Flags &= notallowed; } uint32_t varflags = VARF_Method; int implicitargs = 1; AFuncDesc *afd = nullptr; int useflags = SUF_ACTOR | SUF_OVERLAY | SUF_WEAPON | SUF_ITEM; if (f->UseFlags != nullptr) { useflags = 0; auto p = f->UseFlags; do { switch (p->Id) { case NAME_Actor: useflags |= SUF_ACTOR; break; case NAME_Overlay: useflags |= SUF_OVERLAY; break; case NAME_Weapon: useflags |= SUF_WEAPON; break; case NAME_Item: useflags |= SUF_ITEM; break; default: Error(p, "Unknown Action qualifier %s", FName(p->Id).GetChars()); break; } p = static_cast(p->SiblingNext); } while (p != f->UseFlags); } // map to implementation flags. if (f->Flags & ZCC_Private) varflags |= VARF_Private; if (f->Flags & ZCC_Protected) varflags |= VARF_Protected; if (f->Flags & ZCC_Deprecated) varflags |= VARF_Deprecated; if (f->Flags & ZCC_Virtual) varflags |= VARF_Virtual; if (f->Flags & ZCC_Override) varflags |= VARF_Override; if (f->Flags & ZCC_Abstract) varflags |= VARF_Abstract; if (f->Flags & ZCC_VarArg) varflags |= VARF_VarArg; if (f->Flags & ZCC_FuncConst) varflags |= VARF_ReadOnly; // FuncConst method is internally marked as VARF_ReadOnly if (mVersion >= MakeVersion(2, 4, 0)) { if (c->Type()->ScopeFlags & Scope_UI) varflags |= VARF_UI; if (c->Type()->ScopeFlags & Scope_Play) varflags |= VARF_Play; //if (f->Flags & ZCC_FuncConst) // varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_PlainData); // const implies clearscope. this is checked a bit later to also not have ZCC_Play/ZCC_UIFlag. if (f->Flags & ZCC_UIFlag) varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_UI); if (f->Flags & ZCC_Play) varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Play); if (f->Flags & ZCC_ClearScope) varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Clear); if (f->Flags & ZCC_VirtualScope) varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Virtual); } else { varflags |= VARF_Play; } if ((f->Flags & ZCC_VarArg) && !(f->Flags & ZCC_Native)) { Error(f, "'VarArg' can only be used with native methods"); } if (f->Flags & ZCC_Action) { implicitargs = CheckActionKeyword(f,varflags, useflags, c); if (implicitargs < 0) { Error(f, "'Action' not allowed as a function qualifier"); // Set state to allow continued compilation to find more errors. varflags &= ~VARF_ReadOnly; implicitargs = 1; } } if (f->Flags & ZCC_Static) varflags = (varflags & ~VARF_Method) | VARF_Final, implicitargs = 0; // Static implies Final. if (varflags & VARF_Override) varflags &= ~VARF_Virtual; // allow 'virtual override'. // Only one of these flags may be used. static int exclude[] = { ZCC_Abstract, ZCC_Virtual, ZCC_Override, ZCC_Action, ZCC_Static }; int excludeflags = 0; int fc = 0; for (size_t i = 0; i < countof(exclude); i++) { if (f->Flags & exclude[i]) { fc++; excludeflags |= exclude[i]; } } if (fc > 1) { Error(f, "Invalid combination of qualifiers %s on function %s", FlagsToString(excludeflags).GetChars(), FName(f->Name).GetChars()); varflags |= VARF_Method; } if (varflags & (VARF_Override | VARF_Abstract)) varflags |= VARF_Virtual; // Now that the flags are checked, make all override and abstract functions virtual as well. // [ZZ] this doesn't make sense either. if ((varflags&(VARF_ReadOnly | VARF_Method)) == VARF_ReadOnly) // non-method const function { Error(f, "'Const' on a static method is not supported"); } // [ZZ] neither this if ((varflags&(VARF_VirtualScope | VARF_Method)) == VARF_VirtualScope) // non-method virtualscope function { Error(f, "'VirtualScope' on a static method is not supported"); } static int excludescope[] = { ZCC_UIFlag, ZCC_Play, ZCC_ClearScope, ZCC_VirtualScope }; excludeflags = 0; fc = 0; for (size_t i = 0; i < countof(excludescope); i++) { if (f->Flags & excludescope[i]) { fc++; excludeflags |= excludescope[i]; } } if (fc > 1) { Error(f, "Invalid combination of scope qualifiers %s on function %s", FlagsToString(excludeflags).GetChars(), FName(f->Name).GetChars()); varflags &= ~(VARF_UI | VARF_Play); // make plain data } if (f->Flags & ZCC_Native) { if (varflags & VARF_Abstract) { Error(f, "Native functions cannot be abstract"); return; } varflags |= VARF_Native; afd = FindFunction(c->Type(), FName(f->Name).GetChars()); if (afd == nullptr) { Error(f, "The function '%s.%s' has not been exported from the executable", c->Type()->TypeName.GetChars(), FName(f->Name).GetChars()); } else { (*afd->VMPointer)->ImplicitArgs = uint8_t(implicitargs); } } SetImplicitArgs(&args, &argflags, &argnames, c->Type(), varflags, useflags); argdefaults.Resize(argnames.Size()); auto p = f->Params; bool hasoptionals = false; if (p != nullptr) { bool overridemsg = false; do { int elementcount = 1; TypedVMValue vmval[4]; // default is REGT_NIL which means 'no default value' here. if (p->Type != nullptr) { auto type = DetermineType(c->Type(), p, f->Name, p->Type, false, false); int flags = 0; if ((type->isStruct() && type != TypeVector2 && type != TypeVector3 && type != TypeVector4 && type != TypeQuaternion && type != TypeFVector2 && type != TypeFVector3 && type != TypeFVector4 && type != TypeFQuaternion) || type->isDynArray() || type->isMap() || type->isMapIterator()) { // Structs are being passed by pointer, but unless marked 'out' that pointer must be readonly. type = NewPointer(type /*, !(p->Flags & ZCC_Out)*/); flags |= VARF_Ref; } else if (type->GetRegType() != REGT_NIL) { if (p->Flags & ZCC_Out) flags |= VARF_Out; if (type == TypeVector2 || type == TypeFVector2) { elementcount = 2; } else if (type == TypeVector3 || type == TypeFVector3) { elementcount = 3; } else if (type == TypeVector4 || type == TypeFVector4 || type == TypeQuaternion || type == TypeFQuaternion) { elementcount = 4; } } if (type->GetRegType() == REGT_NIL && type != TypeVector2 && type != TypeVector3 && type != TypeVector4 && type != TypeQuaternion && type != TypeFVector2 && type != TypeFVector3 && type != TypeFVector4 && type != TypeFQuaternion) { // If it's TypeError, then an error was already given if (type != TypeError) { Error(p, "Invalid type %s for function parameter", type->DescriptiveName()); } } else if (p->Default != nullptr) { if (flags & VARF_Out) { Error(p, "Out parameters cannot have default values"); } flags |= VARF_Optional; hasoptionals = true; if ((varflags & VARF_Override) && !overridemsg) { // This is illegal, but in older compilers wasn't checked, so there it has to be demoted to a warning. // Virtual calls always need to get their defaults from the base virtual method. if (mVersion >= MakeVersion(3, 3)) { Error(p, "Default values for parameter of virtual override not allowed"); } else { Warn(p, "Default values for parameter of virtual override will be ignored!"); } overridemsg = true; } FxExpression *x = new FxTypeCast(ConvertNode(p->Default), type, false); FCompileContext ctx(OutNamespace, c->Type(), false, mVersion); x = x->Resolve(ctx); if (x != nullptr) { // Vectors need special treatment because they use more than one entry in the Defaults and do not report as actual constants if ((type == TypeVector2 || type == TypeFVector2) && x->ExprType == EFX_VectorValue && static_cast(x)->isConstVector(2)) { auto vx = static_cast(x); vmval[0] = static_cast(vx->xyzw[0])->GetValue().GetFloat(); vmval[1] = static_cast(vx->xyzw[1])->GetValue().GetFloat(); } else if ((type == TypeVector3 || type == TypeFVector3) && x->ExprType == EFX_VectorValue && static_cast(x)->isConstVector(3)) { auto vx = static_cast(x); vmval[0] = static_cast(vx->xyzw[0])->GetValue().GetFloat(); vmval[1] = static_cast(vx->xyzw[1])->GetValue().GetFloat(); vmval[2] = static_cast(vx->xyzw[2])->GetValue().GetFloat(); } else if ((type == TypeVector4 || type == TypeFVector4) && x->ExprType == EFX_VectorValue && static_cast(x)->isConstVector(4)) { auto vx = static_cast(x); vmval[0] = static_cast(vx->xyzw[0])->GetValue().GetFloat(); vmval[1] = static_cast(vx->xyzw[1])->GetValue().GetFloat(); vmval[2] = static_cast(vx->xyzw[2])->GetValue().GetFloat(); vmval[3] = static_cast(vx->xyzw[3])->GetValue().GetFloat(); } else if ((type == TypeQuaternion || type == TypeFQuaternion) && x->ExprType == EFX_VectorValue && static_cast(x)->isConstVector(4)) { auto vx = static_cast(x); vmval[0] = static_cast(vx->xyzw[0])->GetValue().GetFloat(); vmval[1] = static_cast(vx->xyzw[1])->GetValue().GetFloat(); vmval[2] = static_cast(vx->xyzw[2])->GetValue().GetFloat(); vmval[3] = static_cast(vx->xyzw[3])->GetValue().GetFloat(); } else if (!x->isConstant()) { Error(p, "Default parameter %s is not constant in %s", FName(p->Name).GetChars(), FName(f->Name).GetChars()); } else if (x->ValueType != type) { Error(p, "Default parameter %s could not be converted to target type %s", FName(p->Name).GetChars(), c->Type()->TypeName.GetChars()); } else { auto cnst = static_cast(x); switch (type->GetRegType()) { case REGT_INT: vmval[0] = cnst->GetValue().GetInt(); break; case REGT_FLOAT: vmval[0] = cnst->GetValue().GetFloat(); break; case REGT_POINTER: if (type->isClassPointer()) vmval[0] = (DObject*)cnst->GetValue().GetPointer(); else vmval[0] = cnst->GetValue().GetPointer(); break; case REGT_STRING: // We need a reference to something permanently stored here. vmval[0] = VMStringConstants.Alloc(cnst->GetValue().GetString()); break; default: assert(0 && "no valid type for constant"); break; } } hasdefault = true; } if (x != nullptr) delete x; } else if (hasoptionals) { Error(p, "All arguments after the first optional one need also be optional"); } // TBD: disallow certain types? For now, let everything pass that isn't an array. args.Push(type); argflags.Push(flags); argnames.Push(p->Name); } else { args.Push(nullptr); argflags.Push(0); argnames.Push(NAME_None); } for (int i = 0; i(p->SiblingNext); } while (p != f->Params); } PFunction *sym = Create(c->Type(), f->Name); sym->AddVariant(NewPrototype(rets, args), argflags, argnames, afd == nullptr ? nullptr : *(afd->VMPointer), varflags, useflags); c->Type()->Symbols.ReplaceSymbol(sym); if (f->DeprecationMessage != nullptr) { sym->Variants[0].DeprecationMessage = *f->DeprecationMessage; } auto vcls = PType::toClass(c->Type()); auto cls = vcls ? vcls->Descriptor : nullptr; PFunction *virtsym = nullptr; if (cls != nullptr && cls->ParentClass != nullptr) virtsym = dyn_cast(cls->ParentClass->VMType->Symbols.FindSymbol(FName(f->Name), true)); unsigned vindex = ~0u; if (virtsym != nullptr) { auto imp = virtsym->Variants[0].Implementation; if (imp != nullptr) vindex = imp->VirtualIndex; else Error(f, "Virtual base function %s not found in %s", FName(f->Name).GetChars(), cls->ParentClass->TypeName.GetChars()); } if (f->Flags & (ZCC_Version | ZCC_Deprecated)) { sym->mVersion = f->Version; if (varflags & VARF_Override) { Error(f, "Overridden function %s may not alter version restriction in %s", FName(f->Name).GetChars(), cls->ParentClass->TypeName.GetChars()); } } VMFunction *newfunc = nullptr; if (!(f->Flags & ZCC_Native)) { if (f->Body != nullptr && (varflags & VARF_Abstract)) { Error(f, "Abstract function %s cannot have a body", FName(f->Name).GetChars()); return; } if (f->Body == nullptr && !(varflags & VARF_Abstract)) { Error(f, "Empty function %s", FName(f->Name).GetChars()); return; } FxExpression* code = f->Body != nullptr ? ConvertAST(c->Type(), f->Body) : nullptr; newfunc = FunctionBuildList.AddFunction(OutNamespace, mVersion, sym, code, FStringf("%s.%s", c->Type()->TypeName.GetChars(), FName(f->Name).GetChars()), false, -1, 0, Lump); } if (sym->Variants[0].Implementation != nullptr && hasdefault) // do not copy empty default lists, they only waste space and processing time. { sym->Variants[0].Implementation->DefaultArgs = std::move(argdefaults); } if (sym->Variants[0].Implementation != nullptr) { // [ZZ] unspecified virtual function inherits old scope. virtual function scope can't be changed. sym->Variants[0].Implementation->VarFlags = sym->Variants[0].Flags; } bool exactReturnType = mVersion < MakeVersion(4, 4); PClass *clstype = forclass? static_cast(c->Type())->Descriptor : nullptr; if (varflags & VARF_Virtual) { if (sym->Variants[0].Implementation == nullptr) { Error(f, "Virtual function %s.%s not present", c->Type()->TypeName.GetChars(), FName(f->Name).GetChars()); return; } if (forclass) { if ((varflags & VARF_Abstract) && !clstype->bAbstract) { Error(f, "Abstract functions can only be defined in abstract classes"); return; } auto parentfunc = clstype->ParentClass? dyn_cast(clstype->ParentClass->VMType->Symbols.FindSymbol(sym->SymbolName, true)) : nullptr; int virtindex = clstype->FindVirtualIndex(sym->SymbolName, &sym->Variants[0], parentfunc, exactReturnType); // specifying 'override' is necessary to prevent one of the biggest problem spots with virtual inheritance: Mismatching argument types. if (varflags & VARF_Override) { if (virtindex == -1) { Error(f, "Attempt to override non-existent virtual function %s", FName(f->Name).GetChars()); } else { auto oldfunc = clstype->Virtuals[virtindex]; if (parentfunc && parentfunc->mVersion > mVersion) { Error(f, "Attempt to override function %s which is incompatible with version %d.%d.%d", FName(f->Name).GetChars(), mVersion.major, mVersion.minor, mVersion.revision); } if (oldfunc->VarFlags & VARF_Final) { Error(f, "Attempt to override final function %s", FName(f->Name).GetChars()); } // you can't change ui/play/clearscope for a virtual method. if (f->Flags & (ZCC_UIFlag|ZCC_Play|ZCC_ClearScope|ZCC_VirtualScope)) { Error(f, "Attempt to change scope for virtual function %s", FName(f->Name).GetChars()); } // you can't change const qualifier for a virtual method if ((sym->Variants[0].Implementation->VarFlags & VARF_ReadOnly) && !(oldfunc->VarFlags & VARF_ReadOnly)) { Error(f, "Attempt to add const qualifier to virtual function %s", FName(f->Name).GetChars()); } // you can't change protected qualifier for a virtual method (i.e. putting private), because this cannot be reliably checked without runtime stuff if (f->Flags & (ZCC_Private | ZCC_Protected)) { Error(f, "Attempt to change private/protected qualifiers for virtual function %s", FName(f->Name).GetChars()); } // inherit scope of original function if override not specified sym->Variants[0].Flags = FScopeBarrier::ChangeSideInFlags(sym->Variants[0].Flags, FScopeBarrier::SideFromFlags(oldfunc->VarFlags)); // inherit const from original function if (oldfunc->VarFlags & VARF_ReadOnly) sym->Variants[0].Flags |= VARF_ReadOnly; if (oldfunc->VarFlags & VARF_Protected) sym->Variants[0].Flags |= VARF_Protected; clstype->Virtuals[virtindex] = sym->Variants[0].Implementation; sym->Variants[0].Implementation->VirtualIndex = virtindex; sym->Variants[0].Implementation->VarFlags = sym->Variants[0].Flags; // Defaults must be identical to parent class if (parentfunc->Variants[0].Implementation->DefaultArgs.Size() > 0) { sym->Variants[0].Implementation->DefaultArgs = parentfunc->Variants[0].Implementation->DefaultArgs; sym->Variants[0].ArgFlags = parentfunc->Variants[0].ArgFlags; } // Update argument flags for VM function if needed as their list could be incomplete // At the moment of function creation, arguments with default values were not copied yet from the parent function if (newfunc != nullptr && sym->Variants[0].ArgFlags.Size() != newfunc->ArgFlags.Size()) { for (unsigned i = newfunc->ArgFlags.Size(), count = sym->Variants[0].ArgFlags.Size(); i < count; ++i) { newfunc->ArgFlags.Push(sym->Variants[0].ArgFlags[i]); } newfunc->Proto = sym->Variants[0].Proto; } } } else { if (virtindex != -1) { Error(f, "Function %s attempts to override parent function without 'override' qualifier", FName(f->Name).GetChars()); } sym->Variants[0].Implementation->VirtualIndex = clstype->Virtuals.Push(sym->Variants[0].Implementation); } } else { Error(p, "Virtual functions can only be defined for classes"); } } else if (forclass) { int virtindex = clstype->FindVirtualIndex(sym->SymbolName, &sym->Variants[0], nullptr, exactReturnType); if (virtindex != -1) { Error(f, "Function %s attempts to override parent function without 'override' qualifier", FName(f->Name).GetChars()); } } } } //========================================================================== // // Parses the functions list // //========================================================================== void ZCCCompiler::InitFunctions() { for (auto s : Structs) { for (auto f : s->Functions) { CompileFunction(s, f, false); } } for (auto c : Classes) { // cannot be done earlier because it requires the parent class to be processed by this code, too. if (c->ClassType()->ParentClass != nullptr) { c->ClassType()->Virtuals = c->ClassType()->ParentClass->Virtuals; } for (auto f : c->Functions) { CompileFunction(c, f, true); } // [Player701] Make sure all abstract functions are overridden if (!c->ClassType()->bAbstract) { for (auto v : c->ClassType()->Virtuals) { if (v->VarFlags & VARF_Abstract) { Error(c->cls, "Non-abstract class %s must override abstract function %s", c->Type()->TypeName.GetChars(), v->PrintableName.GetChars()); } } } } } //========================================================================== // // Convert the AST data for the code generator. // //========================================================================== FxExpression *ZCCCompiler::ConvertAST(PContainerType *cls, ZCC_TreeNode *ast) { ConvertClass = cls; // there are two possibilities here: either a single function call or a compound statement. For a compound statement we also need to check if the last thing added was a return. if (ast->NodeType == AST_ExprFuncCall) { auto cp = new FxCompoundStatement(*ast); cp->Add(new FxReturnStatement(ConvertNode(ast), *ast)); return cp; } else { // This must be done here so that we can check for a trailing return statement. auto x = new FxCompoundStatement(*ast); auto compound = static_cast(ast); //bool isreturn = false; auto node = compound->Content; if (node != nullptr) do { x->Add(ConvertNode(node)); //isreturn = node->NodeType == AST_ReturnStmt; node = static_cast(node->SiblingNext); } while (node != compound->Content); //if (!isreturn) x->Add(new FxReturnStatement(nullptr, *ast)); return x; } } #define xx(a,z) z, static int Pex2Tok[] = { #include "zcc_exprlist.h" }; //========================================================================== // // Helper for modify/assign operators // //========================================================================== static FxExpression *ModifyAssign(FxBinary *operation, FxExpression *left) { auto assignself = static_cast(operation->left); auto assignment = new FxAssign(left, operation, true); assignself->Assignment = assignment; return assignment; } //========================================================================== // // Convert an AST node and its children // //========================================================================== FxExpression *ZCCCompiler::ConvertNode(ZCC_TreeNode *ast, bool substitute) { if (ast == nullptr) return nullptr; switch (ast->NodeType) { case AST_ExprFuncCall: { auto fcall = static_cast(ast); // function names can either be // - plain identifiers // - class members // - array syntax for random() calls. // Everything else coming here is a syntax error. FArgumentList args; switch (fcall->Function->NodeType) { case AST_ExprID: // The function name is a simple identifier. return new FxFunctionCall(static_cast(fcall->Function)->Identifier, NAME_None, ConvertNodeList(args, fcall->Parameters), *ast); case AST_ExprMemberAccess: { auto ema = static_cast(fcall->Function); return new FxMemberFunctionCall(ConvertNode(ema->Left, true), ema->Right, ConvertNodeList(args, fcall->Parameters), *ast); } case AST_ExprBinary: // Array syntax for randoms. They are internally stored as ExprBinary with both an identifier on the left and right side. if (fcall->Function->Operation == PEX_ArrayAccess) { auto binary = static_cast(fcall->Function); if (binary->Left->NodeType == AST_ExprID && binary->Right->NodeType == AST_ExprID) { return new FxFunctionCall(static_cast(binary->Left)->Identifier, static_cast(binary->Right)->Identifier, ConvertNodeList(args, fcall->Parameters), *ast); } } // fall through if this isn't an array access node. [[fallthrough]]; default: Error(fcall, "Invalid function identifier"); return new FxNop(*ast); // return something so that the compiler can continue. } break; } case AST_ClassCast: { auto cc = static_cast(ast); if (cc->Parameters == nullptr || cc->Parameters->SiblingNext != cc->Parameters) { Error(cc, "Class type cast requires exactly one parameter"); return new FxNop(*ast); // return something so that the compiler can continue. } auto cls = PClass::FindClass(cc->ClassName); if (cls == nullptr || cls->VMType == nullptr) { Error(cc, "Unknown class %s", FName(cc->ClassName).GetChars()); return new FxNop(*ast); // return something so that the compiler can continue. } return new FxClassPtrCast(cls, ConvertNode(cc->Parameters)); } case AST_StaticArrayStatement: { auto sas = static_cast(ast); PType *ztype = DetermineType(ConvertClass, sas, sas->Id, sas->Type, false, false); FArgumentList args; ConvertNodeList(args, sas->Values); // This has to let the code generator resolve the constants, not the Simplifier, which lacks all the necessary type info. return new FxStaticArray(ztype, sas->Id, args, *ast); } case AST_ExprMemberAccess: { auto memaccess = static_cast(ast); return new FxMemberIdentifier(ConvertNode(memaccess->Left), memaccess->Right, *ast); } case AST_FuncParm: { auto fparm = static_cast(ast); auto node = ConvertNode(fparm->Value); if (fparm->Label != NAME_None) node = new FxNamedNode(fparm->Label, node, *ast); return node; } case AST_ExprID: { auto id = static_cast(ast)->Identifier; if (id == NAME_LevelLocals && substitute) id = NAME_Level; // All static methods of FLevelLocals are now non-static so remap the name right here before passing it to the backend. return new FxIdentifier(id, *ast); } case AST_ExprConstant: { auto cnst = static_cast(ast); if (cnst->Type == TypeName) { return new FxConstant(FName(ENamedName(cnst->IntVal)), *ast); } else if (cnst->Type->isInt()) { if (cnst->Type == TypeUInt32) { return new FxConstant((unsigned)cnst->IntVal, *ast); } else { return new FxConstant(cnst->IntVal, *ast); } } else if (cnst->Type == TypeBool) { return new FxConstant(!!cnst->IntVal, *ast); } else if (cnst->Type->isFloat()) { return new FxConstant(cnst->DoubleVal, *ast); } else if (cnst->Type == TypeString) { return new FxConstant(*cnst->StringVal, *ast); } else if (cnst->Type == TypeNullPtr) { return new FxConstant(*ast); } else { // can there be other types? Error(cnst, "Unknown constant type %s", cnst->Type->DescriptiveName()); return new FxConstant(0, *ast); } } case AST_ExprUnary: { auto unary = static_cast(ast); auto operand = ConvertNode(unary->Operand); auto op = unary->Operation; switch (op) { case PEX_PostDec: case PEX_PostInc: return new FxPostIncrDecr(operand, Pex2Tok[op]); case PEX_PreDec: case PEX_PreInc: return new FxPreIncrDecr(operand, Pex2Tok[op]); case PEX_Negate: return new FxMinusSign(operand); case PEX_AntiNegate: return new FxPlusSign(operand); case PEX_BitNot: return new FxUnaryNotBitwise(operand); case PEX_BoolNot: return new FxUnaryNotBoolean(operand); case PEX_SizeOf: case PEX_AlignOf: return new FxSizeAlign(operand, Pex2Tok[op]); default: assert(0 && "Unknown unary operator."); // should never happen Error(unary, "Unknown unary operator ID #%d", op); return new FxNop(*ast); } break; } case AST_ExprBinary: { auto binary = static_cast(ast); auto left = ConvertNode(binary->Left); auto right = ConvertNode(binary->Right); auto op = binary->Operation; auto tok = Pex2Tok[op]; switch (op) { case PEX_Add: case PEX_Sub: return new FxAddSub(tok, left, right); case PEX_Mul: case PEX_Div: case PEX_Mod: return new FxMulDiv(tok, left, right); case PEX_Pow: return new FxPow(left, right); case PEX_LeftShift: case PEX_RightShift: case PEX_URightShift: return new FxShift(tok, left, right); case PEX_BitAnd: case PEX_BitOr: case PEX_BitXor: return new FxBitOp(tok, left, right); case PEX_BoolOr: case PEX_BoolAnd: return new FxBinaryLogical(tok, left, right); case PEX_LT: case PEX_LTEQ: case PEX_GT: case PEX_GTEQ: return new FxCompareRel(tok, left, right); case PEX_EQEQ: case PEX_NEQ: case PEX_APREQ: return new FxCompareEq(tok, left, right); case PEX_Assign: return new FxAssign(left, right); case PEX_AddAssign: case PEX_SubAssign: return ModifyAssign(new FxAddSub(tok, new FxAssignSelf(*ast), right), left); case PEX_MulAssign: case PEX_DivAssign: case PEX_ModAssign: return ModifyAssign(new FxMulDiv(tok, new FxAssignSelf(*ast), right), left); case PEX_LshAssign: case PEX_RshAssign: case PEX_URshAssign: return ModifyAssign(new FxShift(tok, new FxAssignSelf(*ast), right), left); case PEX_AndAssign: case PEX_OrAssign: case PEX_XorAssign: return ModifyAssign(new FxBitOp(tok, new FxAssignSelf(*ast), right), left); case PEX_LTGTEQ: return new FxLtGtEq(left, right); case PEX_ArrayAccess: return new FxArrayElement(left, right); case PEX_CrossProduct: case PEX_DotProduct: return new FxDotCross(tok, left, right); case PEX_Is: return new FxTypeCheck(left, right); case PEX_Concat: return new FxConcat(left, right); default: I_Error("Binary operator %d not implemented yet", op); } break; } case AST_ExprTrinary: { auto trinary = static_cast(ast); auto condition = ConvertNode(trinary->Test); auto left = ConvertNode(trinary->Left); auto right = ConvertNode(trinary->Right); return new FxConditional(condition, left, right); } case AST_VectorValue: { auto vecini = static_cast(ast); auto xx = ConvertNode(vecini->X); auto yy = ConvertNode(vecini->Y); auto zz = ConvertNode(vecini->Z); auto ww = ConvertNode(vecini->W); return new FxVectorValue(xx, yy, zz, ww, *ast); } case AST_LocalVarStmt: { auto loc = static_cast(ast); auto node = loc->Vars; FxSequence *list = new FxSequence(*ast); PType *ztype = DetermineType(ConvertClass, node, node->Name, loc->Type, true, false); if (loc->Type->ArraySize != nullptr) { bool nosize; ztype = ResolveArraySize(ztype, loc->Type->ArraySize, ConvertClass, &nosize); if (nosize) { Error(node, "Must specify array size"); } } do { PType *type; bool nosize = false; if (node->ArraySize != nullptr) { type = ResolveArraySize(ztype, node->ArraySize, ConvertClass, &nosize); if (nosize && !node->InitIsArray) { Error(node, "Must specify array size for non-initialized arrays"); } } else { type = ztype; } if (node->InitIsArray && (type->isArray() || type->isDynArray() || nosize)) { auto arrtype = static_cast(type); if (!nosize && (arrtype->ElementType->isArray() || arrtype->ElementType->isDynArray())) { Error(node, "Compound initializer not implemented yet for multi-dimensional arrays"); } FArgumentList args; ConvertNodeList(args, node->Init); if (nosize) { type = NewArray(type, args.Size()); } list->Add(new FxLocalArrayDeclaration(type, node->Name, args, 0, *node)); } else { FxExpression *val = node->Init ? ConvertNode(node->Init) : nullptr; list->Add(new FxLocalVariableDeclaration(type, node->Name, val, 0, *node)); // todo: Handle flags in the grammar. } node = static_cast(node->SiblingNext); } while (node != loc->Vars); return list; } case AST_Expression: { auto ret = static_cast(ast); if (ret->Operation == PEX_Super) { return new FxSuper(*ast); } break; } case AST_ExpressionStmt: return ConvertNode(static_cast(ast)->Expression); case AST_ReturnStmt: { auto ret = static_cast(ast); FArgumentList args; ConvertNodeList(args, ret->Values); if (args.Size() == 0) { return new FxReturnStatement(nullptr, *ast); } else { return new FxReturnStatement(args, *ast); } } case AST_BreakStmt: case AST_ContinueStmt: return new FxJumpStatement(ast->NodeType == AST_BreakStmt ? TK_Break : TK_Continue, *ast); case AST_IfStmt: { auto iff = static_cast(ast); FxExpression *const truePath = ConvertImplicitScopeNode(ast, iff->TruePath); FxExpression *const falsePath = ConvertImplicitScopeNode(ast, iff->FalsePath); return new FxIfStatement(ConvertNode(iff->Condition), truePath, falsePath, *ast); } case AST_ArrayIterationStmt: { auto iter = static_cast(ast); auto var = iter->ItName->Name; FxExpression* const itArray = ConvertNode(iter->ItArray); FxExpression* const itArray2 = ConvertNode(iter->ItArray); // the handler needs two copies of this - here's the easiest place to create them. FxExpression* const body = ConvertImplicitScopeNode(ast, iter->LoopStatement); return new FxForEachLoop(iter->ItName->Name, itArray, itArray2, body, *ast); } case AST_IterationStmt: { auto iter = static_cast(ast); if (iter->CheckAt == ZCC_IterationStmt::End) { assert(iter->LoopBumper == nullptr); FxExpression *const loop = ConvertImplicitScopeNode(ast, iter->LoopStatement); return new FxDoWhileLoop(ConvertNode(iter->LoopCondition), loop, *ast); } else if (iter->LoopBumper != nullptr) { FArgumentList bumper; ConvertNodeList(bumper, iter->LoopBumper); FxCompoundStatement *bumps = new FxCompoundStatement(*ast); for (auto &ex : bumper) { bumps->Add(ex); ex = nullptr; } return new FxForLoop(nullptr, ConvertNode(iter->LoopCondition), bumps, ConvertNode(iter->LoopStatement), *ast); } else { FxExpression *const loop = ConvertImplicitScopeNode(ast, iter->LoopStatement); return new FxWhileLoop(ConvertNode(iter->LoopCondition), loop, *ast); } } // not yet done case AST_SwitchStmt: { auto swtch = static_cast(ast); if (swtch->Content->NodeType != AST_CompoundStmt) { Error(ast, "Expecting { after 'switch'"); return new FxNop(*ast); // allow compiler to continue looking for errors. } else { // The switch content is wrapped into a compound statement which needs to be unraveled here. auto cmpnd = static_cast(swtch->Content); FArgumentList args; return new FxSwitchStatement(ConvertNode(swtch->Condition), ConvertNodeList(args, cmpnd->Content), *ast); } } case AST_CaseStmt: { auto cases = static_cast(ast); return new FxCaseStatement(ConvertNode(cases->Condition), *ast); } case AST_CompoundStmt: { auto x = new FxCompoundStatement(*ast); auto compound = static_cast(ast); auto node = compound->Content; if (node != nullptr) do { x->Add(ConvertNode(node)); node = static_cast(node->SiblingNext); } while (node != compound->Content); return x; } case AST_AssignStmt: { auto ass = static_cast(ast); FArgumentList args; ConvertNodeList(args, ass->Dests); assert(ass->Sources->SiblingNext == ass->Sources); // right side should be a single function call - nothing else if (ass->Sources->NodeType != AST_ExprFuncCall) { // don't let this through to the code generator. This node is only used to assign multiple returns of a function to more than one variable. Error(ass, "Right side of multi-assignment must be a function call"); return new FxNop(*ast); // allow compiler to continue looking for errors. } return new FxMultiAssign(args, ConvertNode(ass->Sources), *ast); } case AST_AssignDeclStmt: { auto ass = static_cast(ast); FArgumentList args; { ZCC_TreeNode *n = ass->Dests; if(n) do { args.Push(new FxIdentifier(static_cast(n)->Id,*n)); n = n->SiblingNext; } while(n != ass->Dests); } assert(ass->Sources->SiblingNext == ass->Sources); // right side should be a single function call - nothing else if (ass->Sources->NodeType != AST_ExprFuncCall) { // don't let this through to the code generator. This node is only used to assign multiple returns of a function to more than one variable. Error(ass, "Right side of multi-assignment must be a function call"); return new FxNop(*ast); // allow compiler to continue looking for errors. } return new FxMultiAssignDecl(args, ConvertNode(ass->Sources), *ast); } default: break; } // only for development. I_Error is more convenient here than a normal error. I_Error("ConvertNode encountered unsupported node of type %d", ast->NodeType); return nullptr; } //========================================================================== // // Wrapper around ConvertNode() that adds a scope (a compound statement) // when needed to avoid leaking of variable or contant to an outer scope: // // if (true) int i; else bool b[1]; // while (false) readonly a; // do static const float f[] = {0}; while (false); // // Accessing such variables outside of their statements is now an error // //========================================================================== FxExpression *ZCCCompiler::ConvertImplicitScopeNode(ZCC_TreeNode *node, ZCC_Statement *nested) { assert(nullptr != node); if (nullptr == nested) { return nullptr; } FxExpression *nestedExpr = ConvertNode(nested); assert(nullptr != nestedExpr); const EZCCTreeNodeType nestedType = nested->NodeType; const bool needScope = AST_LocalVarStmt == nestedType || AST_StaticArrayStatement == nestedType; if (needScope) { FxCompoundStatement *implicitCompound = new FxCompoundStatement(*node); implicitCompound->Add(nestedExpr); nestedExpr = implicitCompound; } return nestedExpr; } FArgumentList &ZCCCompiler::ConvertNodeList(FArgumentList &args, ZCC_TreeNode *head) { if (head != nullptr) { auto node = head; do { args.Push(ConvertNode(node)); node = node->SiblingNext; } while (node != head); } return args; }