mirror of
https://github.com/ZDoom/gzdoom-gles.git
synced 2025-01-09 18:50:56 +00:00
a60bdc2bfb
- Since the number of small allocations here is extremely high this will help a lot to prevent fragmentation and since most nodes are collected up front and this is done when no large resources are being loaded it won't cause heap spikes. let Emit methods delete FxExpression arrays when they are done. - For some reason the deletion process does not work 100%, there are always some nodes left behind and so far I haven't found them. This ensures that these arrays do not live any longer than needed.
2938 lines
No EOL
84 KiB
C++
2938 lines
No EOL
84 KiB
C++
/*
|
|
** 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 "a_pickups.h"
|
|
#include "thingdef.h"
|
|
#include "sc_man.h"
|
|
#include "c_console.h"
|
|
#include "c_dispatch.h"
|
|
#include "doomerrors.h"
|
|
#include "w_wad.h"
|
|
#include "cmdlib.h"
|
|
#include "m_alloc.h"
|
|
#include "zcc_parser.h"
|
|
#include "zcc-parse.h"
|
|
#include "zcc_compile.h"
|
|
#include "v_text.h"
|
|
#include "p_lnspec.h"
|
|
#include "i_system.h"
|
|
#include "gdtoa.h"
|
|
#include "codegeneration/codegen.h"
|
|
#include "vmbuilder.h"
|
|
|
|
#define DEFAULTS_VMEXPORT ((BYTE *)(void *)1)
|
|
|
|
//==========================================================================
|
|
//
|
|
// 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.");
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Classes.Push(new ZCC_ClassWork(static_cast<ZCC_Class *>(cnode), treenode));
|
|
cls = Classes.Last();
|
|
}
|
|
|
|
auto node = cnode->Body;
|
|
PSymbolTreeNode *childnode;
|
|
ZCC_Enum *enumType = nullptr;
|
|
|
|
FString name;
|
|
name << "nodes - " << FName(cnode->NodeName);
|
|
cls->TreeNodes.SetName(name);
|
|
|
|
// Need to check if the class actually has a body.
|
|
if (node != nullptr) do
|
|
{
|
|
switch (node->NodeType)
|
|
{
|
|
case AST_Struct:
|
|
case AST_ConstantDef:
|
|
case AST_Enum:
|
|
if ((childnode = AddTreeNode(static_cast<ZCC_NamedNode *>(node)->NodeName, node, &cls->TreeNodes)))
|
|
{
|
|
switch (node->NodeType)
|
|
{
|
|
case AST_Enum:
|
|
enumType = static_cast<ZCC_Enum *>(node);
|
|
cls->Enums.Push(enumType);
|
|
break;
|
|
|
|
case AST_Struct:
|
|
ProcessStruct(static_cast<ZCC_Struct *>(node), childnode, cls->cls);
|
|
break;
|
|
|
|
case AST_ConstantDef:
|
|
cls->Constants.Push(static_cast<ZCC_ConstantDef *>(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<ZCC_VarDeclarator *>(node));
|
|
break;
|
|
|
|
case AST_EnumTerminator:
|
|
enumType = nullptr;
|
|
break;
|
|
|
|
case AST_States:
|
|
cls->States.Push(static_cast<ZCC_States *>(node));
|
|
break;
|
|
|
|
case AST_FuncDeclarator:
|
|
cls->Functions.Push(static_cast<ZCC_FuncDeclarator *>(node));
|
|
break;
|
|
|
|
case AST_Default:
|
|
cls->Defaults.Push(static_cast<ZCC_Default *>(node));
|
|
break;
|
|
|
|
default:
|
|
assert(0 && "Unhandled AST node type");
|
|
break;
|
|
}
|
|
node = node->SiblingNext;
|
|
}
|
|
while (node != cnode->Body);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ZCCCompiler :: ProcessStruct
|
|
//
|
|
//==========================================================================
|
|
|
|
void ZCCCompiler::ProcessStruct(ZCC_Struct *cnode, PSymbolTreeNode *treenode, ZCC_Class *outer)
|
|
{
|
|
Structs.Push(new ZCC_StructWork(static_cast<ZCC_Struct *>(cnode), treenode, outer));
|
|
ZCC_StructWork *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<ZCC_NamedNode *>(node)->NodeName, node, &cls->TreeNodes)))
|
|
{
|
|
switch (node->NodeType)
|
|
{
|
|
case AST_Enum:
|
|
enumType = static_cast<ZCC_Enum *>(node);
|
|
cls->Enums.Push(enumType);
|
|
break;
|
|
|
|
case AST_ConstantDef:
|
|
cls->Constants.Push(static_cast<ZCC_ConstantDef *>(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<ZCC_VarDeclarator *>(node));
|
|
break;
|
|
|
|
case AST_EnumTerminator:
|
|
enumType = nullptr;
|
|
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, PSymbolTable &_outsymbols, int lumpnum)
|
|
: Outer(_outer), GlobalTreeNodes(&_symbols), OutputSymbols(&_outsymbols), AST(ast), Lump(lumpnum)
|
|
{
|
|
FScriptPosition::ResetErrorCounter();
|
|
// Group top-level nodes by type
|
|
if (ast.TopNode != NULL)
|
|
{
|
|
ZCC_TreeNode *node = ast.TopNode;
|
|
PSymbolTreeNode *tnode;
|
|
PType *enumType = nullptr;
|
|
ZCC_Enum *zenumType = nullptr;
|
|
|
|
do
|
|
{
|
|
switch (node->NodeType)
|
|
{
|
|
case AST_Class:
|
|
// a class extension should not check the tree node symbols.
|
|
if (static_cast<ZCC_Class *>(node)->Flags == ZCC_Extension)
|
|
{
|
|
ProcessClass(static_cast<ZCC_Class *>(node), tnode);
|
|
break;
|
|
}
|
|
case AST_Struct:
|
|
case AST_ConstantDef:
|
|
case AST_Enum:
|
|
if ((tnode = AddTreeNode(static_cast<ZCC_NamedNode *>(node)->NodeName, node, GlobalTreeNodes)))
|
|
{
|
|
switch (node->NodeType)
|
|
{
|
|
case AST_Enum:
|
|
zenumType = static_cast<ZCC_Enum *>(node);
|
|
enumType = NewEnum(zenumType->NodeName, nullptr);
|
|
GlobalSymbols.AddSymbol(new PSymbolType(zenumType->NodeName, enumType));
|
|
break;
|
|
|
|
case AST_Class:
|
|
ProcessClass(static_cast<ZCC_Class *>(node), tnode);
|
|
break;
|
|
|
|
case AST_Struct:
|
|
ProcessStruct(static_cast<ZCC_Struct *>(node), tnode, nullptr);
|
|
break;
|
|
|
|
case AST_ConstantDef:
|
|
Constants.Push(static_cast<ZCC_ConstantDef *>(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<PSymbolTreeNode *>(check)->Node, " Original definition is here");
|
|
return nullptr;
|
|
}
|
|
else
|
|
{
|
|
auto sy = new PSymbolTreeNode(name, node);
|
|
FString name;
|
|
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.
|
|
//
|
|
//==========================================================================
|
|
|
|
int ZCCCompiler::Compile()
|
|
{
|
|
CreateClassTypes();
|
|
CreateStructTypes();
|
|
CompileAllConstants();
|
|
CompileAllFields();
|
|
InitDefaults();
|
|
InitFunctions();
|
|
CompileStates();
|
|
return FScriptPosition::ErrorCounter;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ZCCCompiler :: CreateStructTypes
|
|
//
|
|
// Creates a PStruct for every struct.
|
|
//
|
|
//==========================================================================
|
|
|
|
void ZCCCompiler::CreateStructTypes()
|
|
{
|
|
for(auto s : Structs)
|
|
{
|
|
s->Outer = s->OuterDef == nullptr? nullptr : s->OuterDef->Type;
|
|
s->strct->Type = NewStruct(s->NodeName(), s->Outer);
|
|
s->strct->Symbol = new PSymbolType(s->NodeName(), s->Type());
|
|
s->Type()->Symbols.SetName(FName(s->NodeName()));
|
|
GlobalSymbols.AddSymbol(s->strct->Symbol);
|
|
|
|
for (auto e : s->Enums)
|
|
{
|
|
auto etype = NewEnum(e->NodeName, s->Type());
|
|
s->Type()->Symbols.AddSymbol(new PSymbolType(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; i<OrigClasses.Size(); i++)
|
|
{
|
|
auto c = OrigClasses[i];
|
|
// Check if we got the parent already defined.
|
|
PClass *parent;
|
|
auto ParentName = c->cls->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);
|
|
p = static_cast<decltype(p)>(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)
|
|
{
|
|
// 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());
|
|
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 = me;
|
|
auto ac = dyn_cast<PClassActor>(me);
|
|
if (ac != nullptr) ac->SourceLumpName = *c->cls->SourceName;
|
|
}
|
|
else
|
|
{
|
|
// The parent was the last native class in the inheritance tree.
|
|
// There is probably a better place for this somewhere else
|
|
// TODO: Do this somewhere else or find a less hackish way to do it
|
|
if (!parent->bRuntimeClass)
|
|
{
|
|
//assert(parent->VMExported != nullptr); // Ideally the macro would be used on all inheritable-native classes
|
|
/**/ if (parent->VMExported != nullptr) { /**/ // remove the if condition when all done
|
|
|
|
parent = parent->VMExported;
|
|
assert(parent->bRuntimeClass == false);
|
|
|
|
if (parent->Defaults == nullptr)
|
|
{
|
|
// We have to manually do that since zscript knows nothing about these
|
|
parent->Defaults = DEFAULTS_VMEXPORT;
|
|
}
|
|
|
|
/**/ } /**/
|
|
}
|
|
|
|
// We will never get here if the name is a duplicate, so we can just do the assignment.
|
|
c->cls->Type = parent->FindClassTentative(c->NodeName());
|
|
}
|
|
c->Type()->bExported = true; // this class is accessible to script side type casts. (The reason for this flag is that types like PInt need to be skipped.)
|
|
c->cls->Symbol = new PSymbolType(c->NodeName(), c->Type());
|
|
GlobalSymbols.AddSymbol(c->cls->Symbol);
|
|
c->Type()->Symbols.SetName(c->NodeName());
|
|
Classes.Push(c);
|
|
OrigClasses.Delete(i--);
|
|
donesomething = true;
|
|
}
|
|
else
|
|
{
|
|
// 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 = RUNTIME_CLASS(DObject)->FindClassTentative(c->NodeName());
|
|
c->cls->Symbol = new PSymbolType(c->NodeName(), c->Type());
|
|
GlobalSymbols.AddSymbol(c->cls->Symbol);
|
|
c->Type()->Symbols.SetName(c->NodeName());
|
|
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 = RUNTIME_CLASS(DObject)->FindClassTentative(c->NodeName());
|
|
c->cls->Symbol = new PSymbolType(c->NodeName(), c->Type());
|
|
c->Type()->Symbols.SetName(FName(c->NodeName()).GetChars());
|
|
GlobalSymbols.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 and set up replacements
|
|
for (auto cd : Classes)
|
|
{
|
|
for (auto e : cd->Enums)
|
|
{
|
|
auto etype = NewEnum(e->NodeName, cd->Type());
|
|
cd->Type()->Symbols.AddSymbol(new PSymbolType(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->Type() == cd->Type()->ParentClass)
|
|
{
|
|
cd->TreeNodes.SetParentTable(&cc->TreeNodes);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (cd->cls->Replaces != nullptr && !static_cast<PClassActor *>(cd->Type())->SetReplacement(cd->cls->Replaces->Id))
|
|
{
|
|
Warn(cd->cls, "Replaced type '%s' not found for %s", FName(cd->cls->Replaces->Id).GetChars(), cd->Type()->TypeName.GetChars());
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ZCCCompiler :: AddConstants
|
|
//
|
|
// Helper for CompileAllConstants
|
|
//
|
|
//==========================================================================
|
|
|
|
void ZCCCompiler::CopyConstants(TArray<ZCC_ConstantWork> &dest, TArray<ZCC_ConstantDef*> &Constants, PSymbolTable *ot)
|
|
{
|
|
for (auto c : Constants)
|
|
{
|
|
dest.Push({ c, 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<ZCC_ConstantWork> constantwork;
|
|
|
|
CopyConstants(constantwork, Constants, OutputSymbols);
|
|
for (auto c : Classes)
|
|
{
|
|
CopyConstants(constantwork, c->Constants, &c->Type()->Symbols);
|
|
}
|
|
for (auto s : Structs)
|
|
{
|
|
CopyConstants(constantwork, s->Constants, &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; i<constantwork.Size(); i++)
|
|
{
|
|
if (constantwork[i].node->Value->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].node, constantwork[i].outputtable))
|
|
{
|
|
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());
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ZCCCompiler :: AddConstant
|
|
//
|
|
// Adds a constant to its assigned symbol table
|
|
//
|
|
//==========================================================================
|
|
|
|
void ZCCCompiler::AddConstant(ZCC_ConstantWork &constant)
|
|
{
|
|
auto def = constant.node;
|
|
auto val = def->Value;
|
|
if (val->NodeType == AST_ExprConstant)
|
|
{
|
|
ZCC_ExprConstant *cval = static_cast<ZCC_ExprConstant *>(val);
|
|
if (cval->Type == TypeString)
|
|
{
|
|
def->Symbol = new PSymbolConstString(def->NodeName, *(cval->StringVal));
|
|
}
|
|
else if (cval->Type->IsA(RUNTIME_CLASS(PInt)))
|
|
{
|
|
// How do we get an Enum type in here without screwing everything up???
|
|
//auto type = def->Type != nullptr ? def->Type : cval->Type;
|
|
def->Symbol = new PSymbolConstNumeric(def->NodeName, cval->Type, cval->IntVal);
|
|
}
|
|
else if (cval->Type->IsA(RUNTIME_CLASS(PFloat)))
|
|
{
|
|
if (def->Type != nullptr)
|
|
{
|
|
Error(def, "Enum members must be integer values");
|
|
}
|
|
def->Symbol = new PSymbolConstNumeric(def->NodeName, cval->Type, cval->DoubleVal);
|
|
}
|
|
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 = new PSymbolConstNumeric(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_ConstantDef *def, PSymbolTable *sym)
|
|
{
|
|
assert(def->Symbol == nullptr);
|
|
|
|
ZCC_Expression *val = Simplify(def->Value, sym, true);
|
|
def->Value = val;
|
|
return (val->NodeType == AST_ExprConstant);
|
|
}
|
|
|
|
|
|
//==========================================================================
|
|
//
|
|
// ZCCCompiler :: Simplify
|
|
//
|
|
// For an expression,
|
|
// Evaluate operators whose arguments are both constants, replacing it
|
|
// with a new constant.
|
|
// For a binary operator with one constant argument, put it on the right-
|
|
// hand operand, where permitted.
|
|
// Perform automatic type promotion.
|
|
//
|
|
//==========================================================================
|
|
|
|
ZCC_Expression *ZCCCompiler::Simplify(ZCC_Expression *root, PSymbolTable *sym, bool wantconstant)
|
|
{
|
|
SimplifyingConstant = wantconstant;
|
|
return DoSimplify(root, sym);
|
|
}
|
|
|
|
ZCC_Expression *ZCCCompiler::DoSimplify(ZCC_Expression *root, PSymbolTable *sym)
|
|
{
|
|
if (root->NodeType == AST_ExprUnary)
|
|
{
|
|
return SimplifyUnary(static_cast<ZCC_ExprUnary *>(root), sym);
|
|
}
|
|
else if (root->NodeType == AST_ExprBinary)
|
|
{
|
|
return SimplifyBinary(static_cast<ZCC_ExprBinary *>(root), sym);
|
|
}
|
|
else if (root->Operation == PEX_ID)
|
|
{
|
|
return IdentifyIdentifier(static_cast<ZCC_ExprID *>(root), sym);
|
|
}
|
|
else if (root->Operation == PEX_MemberAccess)
|
|
{
|
|
return SimplifyMemberAccess(static_cast<ZCC_ExprMemberAccess *>(root), sym);
|
|
}
|
|
else if (root->Operation == PEX_FuncCall)
|
|
{
|
|
return SimplifyFunctionCall(static_cast<ZCC_ExprFuncCall *>(root), sym);
|
|
}
|
|
return root;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ZCCCompiler :: SimplifyUnary
|
|
//
|
|
//==========================================================================
|
|
|
|
ZCC_Expression *ZCCCompiler::SimplifyUnary(ZCC_ExprUnary *unary, PSymbolTable *sym)
|
|
{
|
|
unary->Operand = DoSimplify(unary->Operand, sym);
|
|
if (unary->Operand->Type == nullptr)
|
|
{
|
|
return unary;
|
|
}
|
|
ZCC_OpProto *op = PromoteUnary(unary->Operation, unary->Operand);
|
|
if (op == NULL)
|
|
{ // Oh, poo!
|
|
unary->Type = TypeError;
|
|
}
|
|
else if (unary->Operand->Operation == PEX_ConstValue)
|
|
{
|
|
return op->EvalConst1(static_cast<ZCC_ExprConstant *>(unary->Operand));
|
|
}
|
|
return unary;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ZCCCompiler :: SimplifyBinary
|
|
//
|
|
//==========================================================================
|
|
|
|
ZCC_Expression *ZCCCompiler::SimplifyBinary(ZCC_ExprBinary *binary, PSymbolTable *sym)
|
|
{
|
|
binary->Left = DoSimplify(binary->Left, sym);
|
|
binary->Right = DoSimplify(binary->Right, sym);
|
|
if (binary->Left->Type == nullptr || binary->Right->Type == nullptr)
|
|
{
|
|
// We do not know yet what this is so we cannot promote it (yet.)
|
|
return binary;
|
|
}
|
|
ZCC_OpProto *op = PromoteBinary(binary->Operation, binary->Left, binary->Right);
|
|
if (op == NULL)
|
|
{
|
|
binary->Type = TypeError;
|
|
}
|
|
else if (binary->Left->Operation == PEX_ConstValue &&
|
|
binary->Right->Operation == PEX_ConstValue)
|
|
{
|
|
return op->EvalConst2(static_cast<ZCC_ExprConstant *>(binary->Left),
|
|
static_cast<ZCC_ExprConstant *>(binary->Right), AST.Strings);
|
|
}
|
|
return binary;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ZCCCompiler :: SimplifyMemberAccess
|
|
//
|
|
//==========================================================================
|
|
|
|
ZCC_Expression *ZCCCompiler::SimplifyMemberAccess(ZCC_ExprMemberAccess *dotop, PSymbolTable *symt)
|
|
{
|
|
PSymbolTable *symtable;
|
|
|
|
// TBD: Is it safe to simplify the left side here when not processing a constant?
|
|
dotop->Left = DoSimplify(dotop->Left, symt);
|
|
|
|
if (dotop->Left->Operation == PEX_TypeRef)
|
|
{ // Type refs can be evaluated now.
|
|
PType *ref = static_cast<ZCC_ExprTypeRef *>(dotop->Left)->RefType;
|
|
PSymbol *sym = ref->Symbols.FindSymbolInTable(dotop->Right, symtable);
|
|
if (sym != nullptr)
|
|
{
|
|
ZCC_Expression *expr = NodeFromSymbol(sym, dotop, symtable);
|
|
if (expr != nullptr)
|
|
{
|
|
return expr;
|
|
}
|
|
}
|
|
}
|
|
else if (dotop->Left->Operation == PEX_Super)
|
|
{
|
|
symt = symt->GetParentTable();
|
|
if (symt != nullptr)
|
|
{
|
|
PSymbol *sym = symt->FindSymbolInTable(dotop->Right, symtable);
|
|
if (sym != nullptr)
|
|
{
|
|
ZCC_Expression *expr = NodeFromSymbol(sym, dotop, symtable);
|
|
if (expr != nullptr)
|
|
{
|
|
return expr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return dotop;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ZCCCompiler :: SimplifyFunctionCall
|
|
//
|
|
// This may replace a function call with cast(s), since they look like the
|
|
// same thing to the parser.
|
|
//
|
|
//==========================================================================
|
|
|
|
ZCC_Expression *ZCCCompiler::SimplifyFunctionCall(ZCC_ExprFuncCall *callop, PSymbolTable *sym)
|
|
{
|
|
ZCC_FuncParm *parm;
|
|
int parmcount = 0;
|
|
|
|
parm = callop->Parameters;
|
|
if (parm != NULL)
|
|
{
|
|
do
|
|
{
|
|
parmcount++;
|
|
assert(parm->NodeType == AST_FuncParm);
|
|
parm->Value = DoSimplify(parm->Value, sym);
|
|
parm = static_cast<ZCC_FuncParm *>(parm->SiblingNext);
|
|
}
|
|
while (parm != callop->Parameters);
|
|
}
|
|
// Only simplify the 'function' part if we want to retrieve a constant.
|
|
// This is necessary to evaluate the type casts, but for actual functions
|
|
// the simplification process is destructive and has to be avoided.
|
|
if (SimplifyingConstant)
|
|
{
|
|
callop->Function = DoSimplify(callop->Function, sym);
|
|
}
|
|
// If the left side is a type ref, then this is actually a cast
|
|
// and not a function call.
|
|
if (callop->Function->Operation == PEX_TypeRef)
|
|
{
|
|
if (parmcount != 1)
|
|
{
|
|
Error(callop, "Type cast requires one parameter");
|
|
callop->ToErrorNode();
|
|
}
|
|
else
|
|
{
|
|
PType *dest = static_cast<ZCC_ExprTypeRef *>(callop->Function)->RefType;
|
|
const PType::Conversion *route[CONVERSION_ROUTE_SIZE];
|
|
int routelen = parm->Value->Type->FindConversion(dest, route, countof(route));
|
|
if (routelen < 0)
|
|
{
|
|
///FIXME: Need real type names
|
|
Error(callop, "Cannot convert %s to %s", parm->Value->Type->DescriptiveName(), dest->DescriptiveName());
|
|
callop->ToErrorNode();
|
|
}
|
|
else
|
|
{
|
|
ZCC_Expression *val = ApplyConversion(parm->Value, route, routelen);
|
|
assert(val->Type == dest);
|
|
return val;
|
|
}
|
|
}
|
|
}
|
|
return callop;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ZCCCompiler :: PromoteUnary
|
|
//
|
|
// Converts the operand into a format preferred by the operator.
|
|
//
|
|
//==========================================================================
|
|
|
|
ZCC_OpProto *ZCCCompiler::PromoteUnary(EZCCExprType op, ZCC_Expression *&expr)
|
|
{
|
|
if (expr->Type == TypeError)
|
|
{
|
|
return NULL;
|
|
}
|
|
const PType::Conversion *route[CONVERSION_ROUTE_SIZE];
|
|
int routelen = countof(route);
|
|
ZCC_OpProto *proto = ZCC_OpInfo[op].FindBestProto(expr->Type, route, routelen);
|
|
|
|
if (proto != NULL)
|
|
{
|
|
expr = ApplyConversion(expr, route, routelen);
|
|
}
|
|
return proto;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ZCCCompiler :: PromoteBinary
|
|
//
|
|
// Converts the operands into a format (hopefully) compatible with the
|
|
// operator.
|
|
//
|
|
//==========================================================================
|
|
|
|
ZCC_OpProto *ZCCCompiler::PromoteBinary(EZCCExprType op, ZCC_Expression *&left, ZCC_Expression *&right)
|
|
{
|
|
// If either operand is of type 'error', the result is also 'error'
|
|
if (left->Type == TypeError || right->Type == TypeError)
|
|
{
|
|
return NULL;
|
|
}
|
|
const PType::Conversion *route1[CONVERSION_ROUTE_SIZE], *route2[CONVERSION_ROUTE_SIZE];
|
|
int route1len = countof(route1), route2len = countof(route2);
|
|
ZCC_OpProto *proto = ZCC_OpInfo[op].FindBestProto(left->Type, route1, route1len, right->Type, route2, route2len);
|
|
if (proto != NULL)
|
|
{
|
|
left = ApplyConversion(left, route1, route1len);
|
|
right = ApplyConversion(right, route2, route2len);
|
|
}
|
|
return proto;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ZCCCompiler :: ApplyConversion
|
|
//
|
|
//==========================================================================
|
|
|
|
ZCC_Expression *ZCCCompiler::ApplyConversion(ZCC_Expression *expr, const PType::Conversion **route, int routelen)
|
|
{
|
|
for (int i = 0; i < routelen; ++i)
|
|
{
|
|
if (expr->Operation != PEX_ConstValue)
|
|
{
|
|
expr = AddCastNode(route[i]->TargetType, expr);
|
|
}
|
|
else
|
|
{
|
|
route[i]->ConvertConstant(static_cast<ZCC_ExprConstant *>(expr), AST.Strings);
|
|
}
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ZCCCompiler :: AddCastNode
|
|
//
|
|
//==========================================================================
|
|
|
|
ZCC_Expression *ZCCCompiler::AddCastNode(PType *type, ZCC_Expression *expr)
|
|
{
|
|
assert(expr->Operation != PEX_ConstValue && "Expression must not be constant");
|
|
// TODO: add a node here
|
|
return expr;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ZCCCompiler :: IdentifyIdentifier
|
|
//
|
|
// Returns a node that represents what the identifer stands for.
|
|
//
|
|
//==========================================================================
|
|
|
|
ZCC_Expression *ZCCCompiler::IdentifyIdentifier(ZCC_ExprID *idnode, PSymbolTable *symt)
|
|
{
|
|
// Check the symbol table for the identifier.
|
|
PSymbolTable *table;
|
|
PSymbol *sym = symt->FindSymbolInTable(idnode->Identifier, table);
|
|
// GlobalSymbols cannot be the parent of a class's symbol table so we have to look for global symbols explicitly.
|
|
if (sym == nullptr && symt != &GlobalSymbols) sym = GlobalSymbols.FindSymbolInTable(idnode->Identifier, table);
|
|
if (sym != nullptr)
|
|
{
|
|
ZCC_Expression *node = NodeFromSymbol(sym, idnode, table);
|
|
if (node != NULL)
|
|
{
|
|
return node;
|
|
}
|
|
}
|
|
else if (SimplifyingConstant) // leave unknown identifiers alone when simplifying non-constants. It is impossible to know what they are here.
|
|
{
|
|
// Also handle line specials.
|
|
// To call this like a function this needs to be done differently, but for resolving constants this is ok.
|
|
int spec = P_FindLineSpecial(FName(idnode->Identifier).GetChars());
|
|
if (spec != 0)
|
|
{
|
|
ZCC_ExprConstant *val = static_cast<ZCC_ExprConstant *>(AST.InitNode(sizeof(*val), AST_ExprConstant, idnode));
|
|
val->Operation = PEX_ConstValue;
|
|
val->Type = TypeSInt32;
|
|
val->IntVal = spec;
|
|
return val;
|
|
}
|
|
|
|
Error(idnode, "Unknown identifier '%s'", FName(idnode->Identifier).GetChars());
|
|
idnode->ToErrorNode();
|
|
}
|
|
return idnode;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// 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<PSymbolConst *>(sym), source);
|
|
}
|
|
else if (sym->IsKindOf(RUNTIME_CLASS(PSymbolType)))
|
|
{
|
|
return NodeFromSymbolType(static_cast<PSymbolType *>(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<ZCC_ExprConstant *>(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<PSymbolConstString *>(sym)->Str);
|
|
val->Type = TypeString;
|
|
}
|
|
else
|
|
{
|
|
val->Type = sym->ValueType;
|
|
if (val->Type != TypeError)
|
|
{
|
|
assert(sym->IsKindOf(RUNTIME_CLASS(PSymbolConstNumeric)));
|
|
if (sym->ValueType->IsKindOf(RUNTIME_CLASS(PInt)))
|
|
{
|
|
val->IntVal = static_cast<PSymbolConstNumeric *>(sym)->Value;
|
|
}
|
|
else
|
|
{
|
|
assert(sym->ValueType->IsKindOf(RUNTIME_CLASS(PFloat)));
|
|
val->DoubleVal = static_cast<PSymbolConstNumeric *>(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<ZCC_ExprTypeRef *>(AST.InitNode(sizeof(*ref), AST_ExprTypeRef, idnode));
|
|
ref->Operation = PEX_TypeRef;
|
|
ref->RefType = sym->Type;
|
|
ref->Type = NewClassPointer(RUNTIME_CLASS(PType));
|
|
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 = this->Structs;
|
|
|
|
// 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 == c->Type() && ac->Size != TentativeClass)
|
|
{
|
|
Error(c->cls, "Trying to add fields to class '%s' with native children", c->Type()->TypeName.GetChars());
|
|
Classes.Delete(i--);
|
|
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]->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]->Type();
|
|
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]->Type()->ParentClass->Size;
|
|
}
|
|
}
|
|
if (CompileFields(type, Classes[i]->Fields, nullptr, &Classes[i]->TreeNodes, false))
|
|
{
|
|
// 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(PStruct *type, TArray<ZCC_VarDeclarator *> &Fields, PClass *Outer, PSymbolTable *TreeNodes, bool forstruct)
|
|
{
|
|
while (Fields.Size() > 0)
|
|
{
|
|
auto field = Fields[0];
|
|
|
|
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_Deprecated : ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract;
|
|
|
|
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_Native)
|
|
{
|
|
// todo: get the native address of this field.
|
|
}
|
|
if (field->Flags & ZCC_Meta)
|
|
{
|
|
varflags |= VARF_ReadOnly; // metadata implies readonly
|
|
// todo: this needs to go into the metaclass and needs some handling
|
|
}
|
|
|
|
if (field->Type->ArraySize != nullptr)
|
|
{
|
|
fieldtype = ResolveArraySize(fieldtype, field->Type->ArraySize, &type->Symbols);
|
|
}
|
|
|
|
auto name = field->Names;
|
|
do
|
|
{
|
|
if (AddTreeNode(name->Name, name, TreeNodes, !forstruct))
|
|
{
|
|
auto thisfieldtype = fieldtype;
|
|
if (name->ArraySize != nullptr)
|
|
{
|
|
thisfieldtype = ResolveArraySize(thisfieldtype, name->ArraySize, &type->Symbols);
|
|
}
|
|
|
|
type->AddField(name->Name, thisfieldtype, varflags);
|
|
}
|
|
name = static_cast<ZCC_VarName*>(name->SiblingNext);
|
|
} while (name != field->Names);
|
|
Fields.Delete(0);
|
|
}
|
|
return Fields.Size() == 0;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// 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", "funcconst", "abstract" };
|
|
FString build;
|
|
|
|
for (int i = 0; i < 12; 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<ZCC_BasicType *>(ztype);
|
|
switch (btype->Type)
|
|
{
|
|
case ZCC_SInt8:
|
|
retval = TypeSInt8;
|
|
break;
|
|
|
|
case ZCC_UInt8:
|
|
retval = TypeUInt8;
|
|
break;
|
|
|
|
case ZCC_SInt16:
|
|
retval = TypeSInt16;
|
|
break;
|
|
|
|
case ZCC_UInt16:
|
|
retval = TypeUInt16;
|
|
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;
|
|
|
|
// Do we really want to allow single precision floats, despite all the problems they cause?
|
|
// These are nearly guaranteed to desync between MSVC and GCC on x87, because GCC does not implement an IEEE compliant mode
|
|
case ZCC_Float32:
|
|
case ZCC_FloatAuto:
|
|
//return TypeFloat32;
|
|
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_State:
|
|
retval = TypeState;
|
|
break;
|
|
|
|
case ZCC_Color:
|
|
retval = TypeColor;
|
|
break;
|
|
|
|
case ZCC_Sound:
|
|
retval = TypeSound;
|
|
break;
|
|
|
|
case ZCC_UserType:
|
|
retval = ResolveUserType(btype, &outertype->Symbols);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AST_MapType:
|
|
if (allowarraytypes)
|
|
{
|
|
Error(field, "%s: Map types not implemented yet", name.GetChars());
|
|
// Todo: Decide what we allow here and if it makes sense to allow more complex constructs.
|
|
auto mtype = static_cast<ZCC_MapType *>(ztype);
|
|
retval = NewMap(DetermineType(outertype, field, name, mtype->KeyType, false, false), DetermineType(outertype, field, name, mtype->ValueType, false, false));
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case AST_DynArrayType:
|
|
if (allowarraytypes)
|
|
{
|
|
Error(field, "%s: Dynamic array types not implemented yet", name.GetChars());
|
|
auto atype = static_cast<ZCC_DynArrayType *>(ztype);
|
|
retval = NewDynArray(DetermineType(outertype, field, name, atype->ElementType, false, false));
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case AST_ClassType:
|
|
{
|
|
auto ctype = static_cast<ZCC_ClassType *>(ztype);
|
|
if (ctype->Restriction == nullptr)
|
|
{
|
|
retval = NewClassPointer(RUNTIME_CLASS(DObject));
|
|
}
|
|
else
|
|
{
|
|
auto sym = outertype->Symbols.FindSymbol(ctype->Restriction->Id, true);
|
|
if (sym == nullptr) sym = GlobalSymbols.FindSymbol(ctype->Restriction->Id, false);
|
|
if (sym == nullptr)
|
|
{
|
|
Error(field, "%s: Unknown identifier", FName(ctype->Restriction->Id).GetChars());
|
|
return TypeError;
|
|
}
|
|
auto typesym = dyn_cast<PSymbolType>(sym);
|
|
if (typesym == nullptr || !typesym->Type->IsKindOf(RUNTIME_CLASS(PClass)))
|
|
{
|
|
Error(field, "%s does not represent a class type", FName(ctype->Restriction->Id).GetChars());
|
|
return TypeError;
|
|
}
|
|
retval = NewClassPointer(static_cast<PClass *>(typesym->Type));
|
|
}
|
|
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
|
|
//
|
|
//==========================================================================
|
|
|
|
PType *ZCCCompiler::ResolveUserType(ZCC_BasicType *type, PSymbolTable *symt)
|
|
{
|
|
// Check the symbol table for the identifier.
|
|
PSymbolTable *table;
|
|
PSymbol *sym = symt->FindSymbolInTable(type->UserType->Id, table);
|
|
// GlobalSymbols cannot be the parent of a class's symbol table so we have to look for global symbols explicitly.
|
|
if (sym == nullptr && symt != &GlobalSymbols) sym = GlobalSymbols.FindSymbolInTable(type->UserType->Id, table);
|
|
if (sym != nullptr && sym->IsKindOf(RUNTIME_CLASS(PSymbolType)))
|
|
{
|
|
auto ptype = static_cast<PSymbolType *>(sym)->Type;
|
|
if (ptype->IsKindOf(RUNTIME_CLASS(PEnum)))
|
|
{
|
|
return TypeSInt32; // hack this to an integer until we can resolve the enum mess.
|
|
}
|
|
if (ptype->IsKindOf(RUNTIME_CLASS(PClass)))
|
|
{
|
|
return NewPointer(ptype, type->isconst);
|
|
}
|
|
return ptype;
|
|
}
|
|
Error(type, "Unable to resolve %s as type.", FName(type->UserType->Id).GetChars());
|
|
return TypeError;
|
|
}
|
|
|
|
|
|
//==========================================================================
|
|
//
|
|
// ZCCCompiler :: ResolveArraySize
|
|
//
|
|
// resolves the array size and returns a matching type.
|
|
//
|
|
//==========================================================================
|
|
|
|
PType *ZCCCompiler::ResolveArraySize(PType *baseType, ZCC_Expression *arraysize, PSymbolTable *sym)
|
|
{
|
|
// The duplicate Simplify call is necessary because if the head node gets replaced there is no way to detect the end of the list otherwise.
|
|
arraysize = Simplify(arraysize, sym, true);
|
|
ZCC_Expression *val;
|
|
do
|
|
{
|
|
val = Simplify(arraysize, sym, true);
|
|
if (val->Operation != PEX_ConstValue || !val->Type->IsA(RUNTIME_CLASS(PInt)))
|
|
{
|
|
Error(arraysize, "Array index must be an integer constant");
|
|
return TypeError;
|
|
}
|
|
int size = static_cast<ZCC_ExprConstant *>(val)->IntVal;
|
|
if (size < 1)
|
|
{
|
|
Error(arraysize, "Array size must be positive");
|
|
return TypeError;
|
|
}
|
|
baseType = NewArray(baseType, size);
|
|
val = static_cast<ZCC_Expression *>(val->SiblingNext);
|
|
} while (val != arraysize);
|
|
return baseType;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ZCCCompiler :: GetInt - Input must be a constant expression
|
|
//
|
|
//==========================================================================
|
|
|
|
int ZCCCompiler::GetInt(ZCC_Expression *expr)
|
|
{
|
|
if (expr->Type == TypeError)
|
|
{
|
|
return 0;
|
|
}
|
|
const PType::Conversion *route[CONVERSION_ROUTE_SIZE];
|
|
int routelen = expr->Type->FindConversion(TypeSInt32, route, countof(route));
|
|
if (routelen < 0)
|
|
{
|
|
Error(expr, "Cannot convert to integer");
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
if (expr->Type->IsKindOf(RUNTIME_CLASS(PFloat)))
|
|
{
|
|
Warn(expr, "Truncation of floating point value");
|
|
}
|
|
auto ex = static_cast<ZCC_ExprConstant *>(ApplyConversion(expr, route, routelen));
|
|
return ex->IntVal;
|
|
}
|
|
}
|
|
|
|
double ZCCCompiler::GetDouble(ZCC_Expression *expr)
|
|
{
|
|
if (expr->Type == TypeError)
|
|
{
|
|
return 0;
|
|
}
|
|
const PType::Conversion *route[CONVERSION_ROUTE_SIZE];
|
|
int routelen = expr->Type->FindConversion(TypeFloat64, route, countof(route));
|
|
if (routelen < 0)
|
|
{
|
|
Error(expr, "Cannot convert to float");
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
auto ex = static_cast<ZCC_ExprConstant *>(ApplyConversion(expr, route, routelen));
|
|
return ex->DoubleVal;
|
|
}
|
|
}
|
|
|
|
const char *ZCCCompiler::GetString(ZCC_Expression *expr, bool silent)
|
|
{
|
|
if (expr->Type == TypeError)
|
|
{
|
|
return nullptr;
|
|
}
|
|
else if (expr->Type->IsKindOf(RUNTIME_CLASS(PString)))
|
|
{
|
|
return static_cast<ZCC_ExprConstant *>(expr)->StringVal->GetChars();
|
|
}
|
|
else if (expr->Type->IsKindOf(RUNTIME_CLASS(PName)))
|
|
{
|
|
// Ugh... What a mess...
|
|
return FName(ENamedName(static_cast<ZCC_ExprConstant *>(expr)->IntVal)).GetChars();
|
|
}
|
|
else
|
|
{
|
|
if (!silent) Error(expr, "Cannot convert to string");
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Parses an actor property's parameters and calls the handler
|
|
//
|
|
//==========================================================================
|
|
|
|
void ZCCCompiler::DispatchProperty(FPropertyInfo *prop, ZCC_PropertyStmt *property, AActor *defaults, Baggage &bag)
|
|
{
|
|
static TArray<FPropParam> params;
|
|
static TArray<FString> strings;
|
|
|
|
params.Clear();
|
|
strings.Clear();
|
|
params.Reserve(1);
|
|
params[0].i = 0;
|
|
if (prop->params[0] != '0')
|
|
{
|
|
if (property->Values == nullptr)
|
|
{
|
|
Error(property, "%s: arguments missing", prop->name);
|
|
return;
|
|
}
|
|
property->Values = Simplify(property->Values, &bag.Info->Symbols, true); // need to do this before the loop so that we can find the head node again.
|
|
const char * p = prop->params;
|
|
auto exp = property->Values;
|
|
|
|
while (true)
|
|
{
|
|
FPropParam conv;
|
|
FPropParam pref;
|
|
|
|
if (exp->NodeType != AST_ExprConstant)
|
|
{
|
|
// If we get TypeError, there has already been a message from deeper down so do not print another one.
|
|
if (exp->Type != TypeError) Error(exp, "%s: non-constant parameter", prop->name);
|
|
return;
|
|
}
|
|
conv.s = nullptr;
|
|
pref.s = nullptr;
|
|
pref.i = -1;
|
|
switch ((*p) & 223)
|
|
{
|
|
|
|
case 'X': // Expression in parentheses or number. We only support the constant here. The function will have to be handled by a separate property to get past the parser.
|
|
conv.i = GetInt(exp);
|
|
params.Push(conv);
|
|
conv.exp = nullptr;
|
|
break;
|
|
|
|
case 'I':
|
|
case 'M': // special case for morph styles in DECORATE . This expression-aware parser will not need this.
|
|
case 'N': // special case for thing activations in DECORATE. This expression-aware parser will not need this.
|
|
conv.i = GetInt(exp);
|
|
break;
|
|
|
|
case 'F':
|
|
conv.d = GetDouble(exp);
|
|
break;
|
|
|
|
case 'Z': // an optional string. Does not allow any numeric value.
|
|
if (!GetString(exp, true))
|
|
{
|
|
// apply this expression to the next argument on the list.
|
|
params.Push(conv);
|
|
params[0].i++;
|
|
p++;
|
|
continue;
|
|
}
|
|
conv.s = GetString(exp);
|
|
break;
|
|
|
|
case 'C': // this parser accepts colors only in string form.
|
|
pref.i = 1;
|
|
case 'S':
|
|
case 'T': // a filtered string (ZScript only parses filtered strings so there's nothing to do here.)
|
|
conv.s = GetString(exp);
|
|
break;
|
|
|
|
case 'L': // Either a number or a list of strings
|
|
if (!GetString(exp, true))
|
|
{
|
|
pref.i = 0;
|
|
conv.i = GetInt(exp);
|
|
}
|
|
else
|
|
{
|
|
pref.i = 1;
|
|
params.Push(pref);
|
|
params[0].i++;
|
|
|
|
do
|
|
{
|
|
conv.s = GetString(exp);
|
|
if (conv.s != nullptr)
|
|
{
|
|
params.Push(conv);
|
|
params[0].i++;
|
|
}
|
|
exp = Simplify(static_cast<ZCC_Expression *>(exp->SiblingNext), &bag.Info->Symbols, true);
|
|
} while (exp != property->Values);
|
|
goto endofparm;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
assert(false);
|
|
break;
|
|
|
|
}
|
|
if (pref.i != -1)
|
|
{
|
|
params.Push(pref);
|
|
params[0].i++;
|
|
}
|
|
params.Push(conv);
|
|
params[0].i++;
|
|
exp = Simplify(static_cast<ZCC_Expression *>(exp->SiblingNext), &bag.Info->Symbols, true);
|
|
endofparm:
|
|
p++;
|
|
// Skip the DECORATE 'no comma' marker
|
|
if (*p == '_') p++;
|
|
|
|
if (*p == 0)
|
|
{
|
|
if (exp != property->Values)
|
|
{
|
|
Error(property, "Too many values for '%s'", prop->name);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
else if (exp == property->Values)
|
|
{
|
|
if (*p < 'a')
|
|
{
|
|
Error(property, "Insufficient parameters for %s", prop->name);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// call the handler
|
|
try
|
|
{
|
|
prop->Handler(defaults, bag.Info, bag, ¶ms[0]);
|
|
}
|
|
catch (CRecoverableError &error)
|
|
{
|
|
Error(property, "%s", error.GetMessage());
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Parses an actor property
|
|
//
|
|
//==========================================================================
|
|
|
|
void ZCCCompiler::ProcessDefaultProperty(PClassActor *cls, ZCC_PropertyStmt *prop, Baggage &bag)
|
|
{
|
|
auto namenode = prop->Prop;
|
|
FString propname;
|
|
|
|
if (namenode->SiblingNext == namenode)
|
|
{
|
|
if (namenode->Id == NAME_DamageFunction)
|
|
{
|
|
auto x = ConvertNode(prop->Values);
|
|
CreateDamageFunction(cls, (AActor *)bag.Info->Defaults, x, false);
|
|
((AActor *)bag.Info->Defaults)->DamageVal = -1;
|
|
return;
|
|
}
|
|
|
|
// a one-name property
|
|
propname = FName(namenode->Id);
|
|
|
|
}
|
|
else if (namenode->SiblingNext->SiblingNext == namenode)
|
|
{
|
|
// a two-name property
|
|
propname << FName(namenode->Id) << "." << FName(static_cast<ZCC_Identifier *>(namenode->SiblingNext)->Id);
|
|
}
|
|
else
|
|
{
|
|
Error(prop, "Property name may at most contain two parts");
|
|
return;
|
|
}
|
|
|
|
|
|
FPropertyInfo *property = FindProperty(propname);
|
|
|
|
if (property != nullptr && property->category != CAT_INFO)
|
|
{
|
|
if (cls->IsDescendantOf(*property->cls))
|
|
{
|
|
DispatchProperty(property, prop, (AActor *)bag.Info->Defaults, bag);
|
|
}
|
|
else
|
|
{
|
|
Error(prop, "'%s' requires an actor of type '%s'\n", propname.GetChars(), (*property->cls)->TypeName.GetChars());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Error(prop, "'%s' is an unknown actor property\n", propname.GetChars());
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Finds a flag and sets or clears it
|
|
//
|
|
//==========================================================================
|
|
|
|
void ZCCCompiler::ProcessDefaultFlag(PClassActor *cls, ZCC_FlagStmt *flg)
|
|
{
|
|
auto namenode = flg->name;
|
|
const char *n1 = FName(namenode->Id).GetChars(), *n2;
|
|
|
|
if (namenode->SiblingNext == namenode)
|
|
{
|
|
// a one-name flag
|
|
n2 = nullptr;
|
|
}
|
|
else if (namenode->SiblingNext->SiblingNext == namenode)
|
|
{
|
|
// a two-name flag
|
|
n2 = FName(static_cast<ZCC_Identifier *>(namenode->SiblingNext)->Id).GetChars();
|
|
}
|
|
else
|
|
{
|
|
Error(flg, "Flag name may at most contain two parts");
|
|
return;
|
|
}
|
|
|
|
auto fd = FindFlag(cls, n1, n2, true);
|
|
if (fd != nullptr)
|
|
{
|
|
if (fd->varflags & VARF_Deprecated)
|
|
{
|
|
Warn(flg, "Deprecated flag '%s%s%s' used", n1, n2 ? "." : "", n2 ? n2 : "");
|
|
}
|
|
if (fd->structoffset == -1)
|
|
{
|
|
HandleDeprecatedFlags((AActor*)cls->Defaults, cls, flg->set, fd->flagbit);
|
|
}
|
|
else
|
|
{
|
|
ModActorFlag((AActor*)cls->Defaults, fd, flg->set);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Error(flg, "Unknown flag '%s%s%s'", n1, n2 ? "." : "", n2 ? n2 : "");
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Parses the default list
|
|
//
|
|
//==========================================================================
|
|
|
|
void ZCCCompiler::InitDefaults()
|
|
{
|
|
for (auto c : Classes)
|
|
{
|
|
// This may be removed if the conditions change, but right now only subclasses of Actor can define a Default block.
|
|
if (!c->Type()->IsDescendantOf(RUNTIME_CLASS(AActor)))
|
|
{
|
|
if (c->Defaults.Size()) Error(c->cls, "%s: Non-actor classes may not have defaults", c->Type()->TypeName.GetChars());
|
|
}
|
|
else
|
|
{
|
|
// This should never happen.
|
|
if (c->Type()->Defaults != nullptr)
|
|
{
|
|
Error(c->cls, "%s already has defaults", c->Type()->TypeName.GetChars());
|
|
}
|
|
// This can only occur if a native parent is not initialized. In all other cases the sorting of the class list should prevent this from ever happening.
|
|
else if (c->Type()->ParentClass->Defaults == nullptr && c->Type() != RUNTIME_CLASS(AActor))
|
|
{
|
|
Error(c->cls, "Parent class %s of %s is not initialized", c->Type()->ParentClass->TypeName.GetChars(), c->Type()->TypeName.GetChars());
|
|
}
|
|
else
|
|
{
|
|
// Copy the parent's defaults and meta data.
|
|
auto ti = static_cast<PClassActor *>(c->Type());
|
|
|
|
// Hack for the DVMObjects as they weren't in the list originally
|
|
// TODO: process them in a non hackish way obviously
|
|
if (ti->ParentClass->Defaults == DEFAULTS_VMEXPORT)
|
|
{
|
|
ti->ParentClass->Defaults = nullptr;
|
|
static_cast<PClassActor *>(ti->ParentClass)->InitializeNativeDefaults();
|
|
ti->ParentClass->ParentClass->DeriveData(ti->ParentClass);
|
|
}
|
|
|
|
ti->InitializeNativeDefaults();
|
|
ti->ParentClass->DeriveData(ti);
|
|
|
|
|
|
Baggage bag;
|
|
#ifdef _DEBUG
|
|
bag.ClassName = c->Type()->TypeName;
|
|
#endif
|
|
bag.Info = ti;
|
|
bag.DropItemSet = false;
|
|
bag.StateSet = false;
|
|
bag.fromDecorate = false;
|
|
bag.CurrentState = 0;
|
|
bag.Lumpnum = c->cls->SourceLump;
|
|
bag.DropItemList = nullptr;
|
|
// The actual script position needs to be set per property.
|
|
|
|
for (auto d : c->Defaults)
|
|
{
|
|
auto content = d->Content;
|
|
if (content != nullptr) do
|
|
{
|
|
switch (content->NodeType)
|
|
{
|
|
case AST_PropertyStmt:
|
|
bag.ScriptPosition.FileName = *content->SourceName;
|
|
bag.ScriptPosition.ScriptLine = content->SourceLoc;
|
|
ProcessDefaultProperty(ti, static_cast<ZCC_PropertyStmt *>(content), bag);
|
|
break;
|
|
|
|
case AST_FlagStmt:
|
|
ProcessDefaultFlag(ti, static_cast<ZCC_FlagStmt *>(content));
|
|
break;
|
|
}
|
|
content = static_cast<decltype(content)>(content->SiblingNext);
|
|
} while (content != d->Content);
|
|
}
|
|
if (bag.DropItemSet)
|
|
{
|
|
bag.Info->SetDropItems(bag.DropItemList);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Parses the functions list
|
|
//
|
|
//==========================================================================
|
|
|
|
void ZCCCompiler::InitFunctions()
|
|
{
|
|
TArray<PType *> rets(1);
|
|
TArray<PType *> args;
|
|
TArray<uint32_t> argflags;
|
|
TArray<VMValue> argdefaults;
|
|
TArray<FName> argnames;
|
|
|
|
for (auto c : Classes)
|
|
{
|
|
for (auto f : c->Functions)
|
|
{
|
|
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->IsKindOf(RUNTIME_CLASS(PStruct)) && type != TypeVector2 && type != TypeVector3)
|
|
{
|
|
// structs and classes only get passed by pointer.
|
|
type = NewPointer(type);
|
|
}
|
|
// TBD: disallow certain types? For now, let everything pass that isn't an array.
|
|
rets.Push(type);
|
|
t = static_cast<decltype(t)>(t->SiblingNext);
|
|
} while (t != f->Type);
|
|
}
|
|
|
|
int notallowed = ZCC_Latent | ZCC_Meta | ZCC_ReadOnly | ZCC_FuncConst | ZCC_Abstract;
|
|
|
|
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;
|
|
|
|
// 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_Action) varflags |= VARF_Action|VARF_Final, implicitargs = 3; // Action implies Final.
|
|
if (f->Flags & ZCC_Static) varflags = (varflags & ~VARF_Method) | VARF_Final, implicitargs = 0; // Static implies Final.
|
|
if ((f->Flags & (ZCC_Action | ZCC_Static)) == (ZCC_Action | ZCC_Static))
|
|
{
|
|
Error(f, "%s: Action and Static on the same function is not allowed.", FName(f->Name).GetChars());
|
|
varflags |= VARF_Method;
|
|
}
|
|
|
|
if (f->Flags & ZCC_Native)
|
|
{
|
|
varflags |= VARF_Native;
|
|
afd = FindFunction(FName(f->Name).GetChars());
|
|
if (afd == nullptr)
|
|
{
|
|
Error(f, "The function '%s' has not been exported from the executable.", FName(f->Name).GetChars());
|
|
}
|
|
else
|
|
{
|
|
(*afd->VMPointer)->ImplicitArgs = BYTE(implicitargs);
|
|
}
|
|
}
|
|
SetImplicitArgs(&args, &argflags, &argnames, c->Type(), varflags);
|
|
argdefaults.Resize(argnames.Size());
|
|
auto p = f->Params;
|
|
bool hasoptionals = false;
|
|
if (p != nullptr)
|
|
{
|
|
do
|
|
{
|
|
int elementcount = 1;
|
|
VMValue vmval[3]; // 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 (p->Flags & ZCC_In) flags |= VARF_In;
|
|
if (p->Flags & ZCC_Out) flags |= VARF_Out;
|
|
if ((type->IsA(RUNTIME_CLASS(PStruct))) || (flags & VARF_Out))
|
|
{
|
|
// 'out' parameters and all structs except vectors are passed by reference
|
|
if ((flags & VARF_Out) || (type != TypeVector2 && type != TypeVector3))
|
|
{
|
|
type = NewPointer(type);
|
|
}
|
|
else if (type == TypeVector2)
|
|
{
|
|
elementcount = 2;
|
|
}
|
|
else if (type == TypeVector3)
|
|
{
|
|
elementcount = 3;
|
|
}
|
|
}
|
|
if (type->GetRegType() == REGT_NIL && type != TypeVector2 && type != TypeVector3)
|
|
{
|
|
Error(p, "Invalid type %s for function parameter", type->DescriptiveName());
|
|
}
|
|
else if (p->Default != nullptr)
|
|
{
|
|
flags |= VARF_Optional;
|
|
hasoptionals = true;
|
|
// The simplifier is not suited to convert the constant into something usable.
|
|
// All it does is reduce the expression to a constant but we still got to do proper type checking and conversion.
|
|
// It will also lose important type info about enums, once these get implemented
|
|
// The code generator can do this properly for us.
|
|
FxExpression *x = new FxTypeCast(ConvertNode(p->Default), type, false);
|
|
FCompileContext ctx(c->Type(), false);
|
|
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 && x->ExprType == EFX_VectorValue && static_cast<FxVectorValue *>(x)->isConstVector(2))
|
|
{
|
|
auto vx = static_cast<FxVectorValue *>(x);
|
|
vmval[0] = static_cast<FxConstant *>(vx->xyz[0])->GetValue().GetFloat();
|
|
vmval[1] = static_cast<FxConstant *>(vx->xyz[1])->GetValue().GetFloat();
|
|
}
|
|
else if (type == TypeVector3 && x->ExprType == EFX_VectorValue && static_cast<FxVectorValue *>(x)->isConstVector(3))
|
|
{
|
|
auto vx = static_cast<FxVectorValue *>(x);
|
|
vmval[0] = static_cast<FxConstant *>(vx->xyz[0])->GetValue().GetFloat();
|
|
vmval[1] = static_cast<FxConstant *>(vx->xyz[1])->GetValue().GetFloat();
|
|
vmval[2] = static_cast<FxConstant *>(vx->xyz[2])->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<FxConstant *>(x);
|
|
hasdefault = true;
|
|
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->IsKindOf(RUNTIME_CLASS(PClassPointer)))
|
|
vmval[0] = (DObject*)cnst->GetValue().GetPointer();
|
|
else
|
|
vmval[0] = cnst->GetValue().GetPointer();
|
|
break;
|
|
|
|
case REGT_STRING:
|
|
vmval[0] = cnst->GetValue().GetString();
|
|
break;
|
|
|
|
default:
|
|
assert(0 && "no valid type for constant");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
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<elementcount;i++) argdefaults.Push(vmval[i]);
|
|
p = static_cast<decltype(p)>(p->SiblingNext);
|
|
} while (p != f->Params);
|
|
}
|
|
|
|
PFunction *sym = new PFunction(c->Type(), f->Name);
|
|
sym->AddVariant(NewPrototype(rets, args), argflags, argnames, afd == nullptr? nullptr : *(afd->VMPointer), varflags);
|
|
c->Type()->Symbols.ReplaceSymbol(sym);
|
|
|
|
if (!(f->Flags & ZCC_Native))
|
|
{
|
|
auto code = ConvertAST(c->Type(), f->Body);
|
|
if (code != nullptr)
|
|
{
|
|
sym->Variants[0].Implementation = FunctionBuildList.AddFunction(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);
|
|
}
|
|
// todo: Check inheritance.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// very complicated check for random duration.
|
|
//
|
|
//==========================================================================
|
|
|
|
static bool CheckRandom(ZCC_Expression *duration)
|
|
{
|
|
if (duration->NodeType != AST_ExprFuncCall) return false;
|
|
auto func = static_cast<ZCC_ExprFuncCall *>(duration);
|
|
if (func->Function == nullptr) return false;
|
|
if (func->Function->NodeType != AST_ExprID) return false;
|
|
auto f2 = static_cast<ZCC_ExprID *>(func->Function);
|
|
return f2->Identifier == NAME_Random;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Sets up the action function call
|
|
//
|
|
//==========================================================================
|
|
FxExpression *ZCCCompiler::SetupActionFunction(PClass *cls, ZCC_TreeNode *af)
|
|
{
|
|
// We have 3 cases to consider here:
|
|
// 1. A function without parameters. This can be called directly
|
|
// 2. A functon with parameters. This needs to be wrapped into a helper function to set everything up.
|
|
// 3. An anonymous function.
|
|
|
|
// 1. and 2. are exposed through AST_ExprFunctionCall
|
|
if (af->NodeType == AST_ExprFuncCall)
|
|
{
|
|
auto fc = static_cast<ZCC_ExprFuncCall *>(af);
|
|
assert(fc->Function->NodeType == AST_ExprID);
|
|
auto id = static_cast<ZCC_ExprID *>(fc->Function);
|
|
|
|
// We must skip ACS_NamedExecuteWithResult here, because this name both exists as an action function and as a builtin.
|
|
// The code which gets called from here can easily make use of the builtin, so let's just pass this name without any checks.
|
|
// The actual action function is still needed by DECORATE:
|
|
if (id->Identifier != NAME_ACS_NamedExecuteWithResult)
|
|
{
|
|
PFunction *afd = dyn_cast<PFunction>(cls->Symbols.FindSymbol(id->Identifier, true));
|
|
if (afd != nullptr)
|
|
{
|
|
if (fc->Parameters == nullptr && !(afd->Variants[0].Flags & VARF_Virtual))
|
|
{
|
|
// We can use this function directly without wrapping it in a caller.
|
|
return new FxVMFunctionCall(new FxSelf(*af), afd, FArgumentList(), *af, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// it may also be an action special so check that first before printing an error.
|
|
if (!P_FindLineSpecial(FName(id->Identifier).GetChars()))
|
|
{
|
|
Error(af, "%s: action function not found in %s", FName(id->Identifier).GetChars(), cls->TypeName.GetChars());
|
|
return nullptr;
|
|
}
|
|
// Action specials fall through to the code generator.
|
|
}
|
|
}
|
|
}
|
|
return ConvertAST(cls, af);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Compile the states
|
|
//
|
|
//==========================================================================
|
|
|
|
void ZCCCompiler::CompileStates()
|
|
{
|
|
for (auto c : Classes)
|
|
{
|
|
|
|
if (!c->Type()->IsDescendantOf(RUNTIME_CLASS(AActor)))
|
|
{
|
|
if (c->States.Size()) Error(c->cls, "%s: States can only be defined for actors.", c->Type()->TypeName.GetChars());
|
|
continue;
|
|
}
|
|
|
|
// Same here, hack in the DVMObject as they weren't in the list originally
|
|
// TODO: process them in a non hackish way obviously
|
|
if (c->Type()->bRuntimeClass == true && c->Type()->ParentClass->bRuntimeClass == false)
|
|
{
|
|
auto vmtype = static_cast<PClassActor *>(c->Type()->ParentClass);
|
|
if (vmtype->StateList == nullptr)
|
|
{
|
|
FStateDefinitions vmstates;
|
|
vmstates.MakeStateDefines(dyn_cast<PClassActor>(vmtype->ParentClass));
|
|
vmtype->Finalize(vmstates);
|
|
}
|
|
}
|
|
|
|
FString statename; // The state builder wants the label as one complete string, not separated into tokens.
|
|
FStateDefinitions statedef;
|
|
statedef.MakeStateDefines(dyn_cast<PClassActor>(c->Type()->ParentClass));
|
|
int numframes = 0;
|
|
|
|
for (auto s : c->States)
|
|
{
|
|
auto st = s->Body;
|
|
if (st != nullptr) do
|
|
{
|
|
switch (st->NodeType)
|
|
{
|
|
case AST_StateLabel:
|
|
{
|
|
auto sl = static_cast<ZCC_StateLabel *>(st);
|
|
statename = FName(sl->Label);
|
|
statedef.AddStateLabel(statename);
|
|
break;
|
|
}
|
|
case AST_StateLine:
|
|
{
|
|
auto sl = static_cast<ZCC_StateLine *>(st);
|
|
FState state;
|
|
memset(&state, 0, sizeof(state));
|
|
if (sl->Sprite->Len() != 4)
|
|
{
|
|
Error(sl, "Sprite name must be exactly 4 characters. Found '%s'", sl->Sprite->GetChars());
|
|
}
|
|
else
|
|
{
|
|
state.sprite = GetSpriteIndex(sl->Sprite->GetChars());
|
|
}
|
|
// It is important to call CheckRandom before Simplify, because Simplify will resolve the function's name to nonsense
|
|
if (CheckRandom(sl->Duration))
|
|
{
|
|
auto func = static_cast<ZCC_ExprFuncCall *>(sl->Duration);
|
|
if (func->Parameters == func->Parameters->SiblingNext || func->Parameters != func->Parameters->SiblingNext->SiblingNext)
|
|
{
|
|
Error(sl, "Random duration requires exactly 2 parameters");
|
|
}
|
|
auto p1 = Simplify(func->Parameters->Value, &c->Type()->Symbols, true);
|
|
auto p2 = Simplify(static_cast<ZCC_FuncParm *>(func->Parameters->SiblingNext)->Value, &c->Type()->Symbols, true);
|
|
int v1 = GetInt(p1);
|
|
int v2 = GetInt(p2);
|
|
if (v1 > v2) std::swap(v1, v2);
|
|
state.Tics = (int16_t)clamp<int>(v1, 0, INT16_MAX);
|
|
state.TicRange = (uint16_t)clamp<int>(v2 - v1, 0, UINT16_MAX);
|
|
}
|
|
else
|
|
{
|
|
auto duration = Simplify(sl->Duration, &c->Type()->Symbols, true);
|
|
if (duration->Operation == PEX_ConstValue)
|
|
{
|
|
state.Tics = (int16_t)clamp<int>(GetInt(duration), -1, INT16_MAX);
|
|
state.TicRange = 0;
|
|
}
|
|
else
|
|
{
|
|
Error(sl, "Duration is not a constant");
|
|
}
|
|
}
|
|
state.Fullbright = sl->bBright;
|
|
state.Fast = sl->bFast;
|
|
state.Slow = sl->bSlow;
|
|
state.CanRaise = sl->bCanRaise;
|
|
if ((state.NoDelay = sl->bNoDelay))
|
|
{
|
|
if (statedef.GetStateLabelIndex(NAME_Spawn) != statedef.GetStateCount())
|
|
{
|
|
Warn(sl, "NODELAY only has an effect on the first state after 'Spawn:'");
|
|
}
|
|
}
|
|
if (sl->Offset != nullptr)
|
|
{
|
|
auto o1 = static_cast<ZCC_Expression *>(Simplify(sl->Offset, &c->Type()->Symbols, true));
|
|
auto o2 = static_cast<ZCC_Expression *>(Simplify(static_cast<ZCC_Expression *>(o1->SiblingNext), &c->Type()->Symbols, true));
|
|
|
|
if (o1->Operation != PEX_ConstValue || o2->Operation != PEX_ConstValue)
|
|
{
|
|
Error(o1, "State offsets must be constant");
|
|
}
|
|
else
|
|
{
|
|
state.Misc1 = GetInt(o1);
|
|
state.Misc2 = GetInt(o2);
|
|
}
|
|
}
|
|
#ifdef DYNLIGHT
|
|
if (sl->Lights != nullptr)
|
|
{
|
|
auto l = sl->Lights;
|
|
do
|
|
{
|
|
AddStateLight(&state, GetString(l));
|
|
l = static_cast<decltype(l)>(l->SiblingNext);
|
|
} while (l != sl->Lights);
|
|
}
|
|
#endif
|
|
|
|
if (sl->Action != nullptr)
|
|
{
|
|
auto code = SetupActionFunction(static_cast<PClassActor *>(c->Type()), sl->Action);
|
|
if (code != nullptr)
|
|
{
|
|
auto funcsym = CreateAnonymousFunction(c->Type(), nullptr, VARF_Method | VARF_Action);
|
|
state.ActionFunc = FunctionBuildList.AddFunction(funcsym, code, FStringf("%s.StateFunction.%d", c->Type()->TypeName.GetChars(), statedef.GetStateCount()), false, statedef.GetStateCount(), (int)sl->Frames->Len(), Lump);
|
|
}
|
|
}
|
|
|
|
int count = statedef.AddStates(&state, sl->Frames->GetChars());
|
|
if (count < 0)
|
|
{
|
|
Error(sl, "Invalid frame character string '%s'", sl->Frames->GetChars());
|
|
count = -count;
|
|
}
|
|
break;
|
|
}
|
|
case AST_StateGoto:
|
|
{
|
|
auto sg = static_cast<ZCC_StateGoto *>(st);
|
|
statename = "";
|
|
if (sg->Qualifier != nullptr)
|
|
{
|
|
statename << FName(sg->Qualifier->Id) << "::";
|
|
}
|
|
auto part = sg->Label;
|
|
do
|
|
{
|
|
statename << FName(part->Id) << '.';
|
|
part = static_cast<decltype(part)>(part->SiblingNext);
|
|
} while (part != sg->Label);
|
|
statename.Truncate((long)statename.Len() - 1); // remove the last '.' in the label name
|
|
if (sg->Offset != nullptr)
|
|
{
|
|
auto ofs = Simplify(sg->Offset, &c->Type()->Symbols, true);
|
|
if (ofs->Operation != PEX_ConstValue)
|
|
{
|
|
Error(sg, "Constant offset expected for GOTO");
|
|
}
|
|
else
|
|
{
|
|
int offset = GetInt(ofs);
|
|
if (offset < 0)
|
|
{
|
|
Error(sg, "GOTO offset must be positive");
|
|
offset = 0;
|
|
}
|
|
if (offset > 0)
|
|
{
|
|
statename.AppendFormat("+%d", offset);
|
|
}
|
|
}
|
|
}
|
|
if (!statedef.SetGotoLabel(statename))
|
|
{
|
|
Error(sg, "GOTO before first state");
|
|
}
|
|
break;
|
|
}
|
|
case AST_StateFail:
|
|
case AST_StateWait:
|
|
if (!statedef.SetWait())
|
|
{
|
|
Error(st, "%s before first state", st->NodeType == AST_StateFail ? "Fail" : "Wait");
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case AST_StateLoop:
|
|
if (!statedef.SetLoop())
|
|
{
|
|
Error(st, "LOOP before first state");
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case AST_StateStop:
|
|
if (!statedef.SetStop())
|
|
{
|
|
Error(st, "STOP before first state");
|
|
}
|
|
break;
|
|
|
|
default:
|
|
assert(0 && "Bad AST node in state");
|
|
}
|
|
st = static_cast<decltype(st)>(st->SiblingNext);
|
|
} while (st != s->Body);
|
|
}
|
|
try
|
|
{
|
|
static_cast<PClassActor *>(c->Type())->Finalize(statedef);
|
|
}
|
|
catch (CRecoverableError &err)
|
|
{
|
|
Error(c->cls, "%s", err.GetMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Convert the AST data for the code generator.
|
|
//
|
|
//==========================================================================
|
|
|
|
FxExpression *ZCCCompiler::ConvertAST(PClass *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)
|
|
{
|
|
return new FxReturnStatement(ConvertNode(ast), *ast);
|
|
}
|
|
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<ZCC_CompoundStmt *>(ast);
|
|
//bool isreturn = false;
|
|
auto node = compound->Content;
|
|
if (node != nullptr) do
|
|
{
|
|
x->Add(ConvertNode(node));
|
|
//isreturn = node->NodeType == AST_ReturnStmt;
|
|
node = static_cast<decltype(node)>(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<FxAssignSelf *>(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)
|
|
{
|
|
if (ast == nullptr) return nullptr;
|
|
|
|
// Note: Do not call 'Simplify' here because that function tends to destroy identifiers due to lack of context in which to resolve them.
|
|
// The Fx nodes created here will be better suited for that.
|
|
switch (ast->NodeType)
|
|
{
|
|
case AST_ExprFuncCall:
|
|
{
|
|
auto fcall = static_cast<ZCC_ExprFuncCall *>(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<ZCC_ExprID *>(fcall->Function)->Identifier, NAME_None, ConvertNodeList(args, fcall->Parameters), *ast);
|
|
|
|
case AST_ExprMemberAccess:
|
|
{
|
|
auto ema = static_cast<ZCC_ExprMemberAccess *>(fcall->Function);
|
|
return new FxMemberFunctionCall(ConvertNode(ema->Left), 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<ZCC_ExprBinary *>(fcall->Function);
|
|
if (binary->Left->NodeType == AST_ExprID && binary->Right->NodeType == AST_ExprID)
|
|
{
|
|
return new FxFunctionCall(static_cast<ZCC_ExprID *>(binary->Left)->Identifier, static_cast<ZCC_ExprID *>(binary->Right)->Identifier, ConvertNodeList(args, fcall->Parameters), *ast);
|
|
}
|
|
}
|
|
// fall through if this isn't an array access node.
|
|
|
|
default:
|
|
Error(fcall, "Invalid function identifier");
|
|
return new FxNop(*ast); // return something so that the compiler can continue.
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AST_ExprMemberAccess:
|
|
{
|
|
auto memaccess = static_cast<ZCC_ExprMemberAccess *>(ast);
|
|
return new FxMemberIdentifier(ConvertNode(memaccess->Left), memaccess->Right, *ast);
|
|
}
|
|
|
|
case AST_FuncParm:
|
|
{
|
|
auto fparm = static_cast<ZCC_FuncParm *>(ast);
|
|
// ignore the label for now, that's stuff for far later, when a bit more here is working.
|
|
return ConvertNode(fparm->Value);
|
|
}
|
|
|
|
case AST_ExprID:
|
|
{
|
|
auto id = static_cast<ZCC_ExprID *>(ast);
|
|
return new FxIdentifier(id->Identifier, *ast);
|
|
}
|
|
|
|
case AST_ExprConstant:
|
|
{
|
|
auto cnst = static_cast<ZCC_ExprConstant *>(ast);
|
|
if (cnst->Type->IsA(RUNTIME_CLASS(PName)))
|
|
{
|
|
return new FxConstant(FName(ENamedName(cnst->IntVal)), *ast);
|
|
}
|
|
else if (cnst->Type->IsA(RUNTIME_CLASS(PInt)))
|
|
{
|
|
return new FxConstant(cnst->IntVal, *ast);
|
|
}
|
|
else if (cnst->Type->IsA(RUNTIME_CLASS(PBool)))
|
|
{
|
|
return new FxConstant(!!cnst->IntVal, *ast);
|
|
}
|
|
else if (cnst->Type->IsA(RUNTIME_CLASS(PFloat)))
|
|
{
|
|
return new FxConstant(cnst->DoubleVal, *ast);
|
|
}
|
|
else if (cnst->Type->IsA(RUNTIME_CLASS(PString)))
|
|
{
|
|
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<ZCC_ExprUnary *>(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<ZCC_ExprBinary *>(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:
|
|
case PEX_BitAnd:
|
|
case PEX_BitOr:
|
|
case PEX_BitXor:
|
|
return new FxBinaryInt(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:
|
|
case PEX_AndAssign:
|
|
case PEX_OrAssign:
|
|
case PEX_XorAssign:
|
|
return ModifyAssign(new FxBinaryInt(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);
|
|
|
|
// todo: These do not have representations in DECORATE and no implementation exists yet.
|
|
case PEX_Concat:
|
|
|
|
default:
|
|
I_Error("Binary operator %d not implemented yet", op);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AST_ExprTrinary:
|
|
{
|
|
auto trinary = static_cast<ZCC_ExprTrinary *>(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<ZCC_VectorValue *>(ast);
|
|
auto xx = ConvertNode(vecini->X);
|
|
auto yy = ConvertNode(vecini->Y);
|
|
auto zz = ConvertNode(vecini->Z);
|
|
return new FxVectorValue(xx, yy, zz, *ast);
|
|
}
|
|
|
|
case AST_LocalVarStmt:
|
|
{
|
|
auto loc = static_cast<ZCC_LocalVarStmt *>(ast);
|
|
auto node = loc->Vars;
|
|
FxSequence *list = new FxSequence(*ast);
|
|
do
|
|
{
|
|
// Type determination must be done for each field to properly handle array definitions.
|
|
PType *type = DetermineType(ConvertClass, node, node->Name, loc->Type, true, false);
|
|
if (type->IsKindOf(RUNTIME_CLASS(PArray)))
|
|
{
|
|
Error(loc, "Local array variables not implemented yet.");
|
|
}
|
|
else
|
|
{
|
|
FxExpression *val;
|
|
if (node->InitIsArray)
|
|
{
|
|
Error(node, "Tried to initialize %s with an array", FName(node->Name).GetChars());
|
|
val = nullptr;
|
|
}
|
|
else
|
|
{
|
|
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<decltype(node)>(node->SiblingNext);
|
|
} while (node != loc->Vars);
|
|
return list;
|
|
}
|
|
|
|
case AST_ExpressionStmt:
|
|
return ConvertNode(static_cast<ZCC_ExpressionStmt *>(ast)->Expression);
|
|
|
|
case AST_ReturnStmt:
|
|
{
|
|
auto ret = static_cast<ZCC_ReturnStmt *>(ast);
|
|
FArgumentList args;
|
|
ConvertNodeList(args, ret->Values);
|
|
if (args.Size() == 0)
|
|
{
|
|
return new FxReturnStatement(nullptr, *ast);
|
|
}
|
|
else if (args.Size() == 1)
|
|
{
|
|
auto arg = args[0];
|
|
args[0] = nullptr;
|
|
return new FxReturnStatement(arg, *ast);
|
|
}
|
|
else
|
|
{
|
|
Error(ast, "Return with multiple values not implemented yet.");
|
|
return new FxReturnStatement(nullptr, *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<ZCC_IfStmt *>(ast);
|
|
return new FxIfStatement(ConvertNode(iff->Condition), ConvertNode(iff->TruePath), ConvertNode(iff->FalsePath), *ast);
|
|
}
|
|
|
|
case AST_IterationStmt:
|
|
{
|
|
auto iter = static_cast<ZCC_IterationStmt *>(ast);
|
|
if (iter->CheckAt == ZCC_IterationStmt::End)
|
|
{
|
|
assert(iter->LoopBumper == nullptr);
|
|
return new FxDoWhileLoop(ConvertNode(iter->LoopCondition), ConvertNode(iter->LoopStatement), *ast);
|
|
}
|
|
else if (iter->LoopBumper != nullptr)
|
|
{
|
|
return new FxForLoop(nullptr, ConvertNode(iter->LoopCondition), ConvertNode(iter->LoopBumper), ConvertNode(iter->LoopStatement), *ast);
|
|
}
|
|
else
|
|
{
|
|
return new FxWhileLoop(ConvertNode(iter->LoopCondition), ConvertNode(iter->LoopStatement), *ast);
|
|
}
|
|
}
|
|
|
|
// not yet done
|
|
case AST_SwitchStmt:
|
|
{
|
|
auto swtch = static_cast<ZCC_SwitchStmt *>(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<ZCC_CompoundStmt *>(swtch->Content);
|
|
FArgumentList args;
|
|
return new FxSwitchStatement(ConvertNode(swtch->Condition), ConvertNodeList(args, cmpnd->Content), *ast);
|
|
}
|
|
}
|
|
|
|
case AST_CaseStmt:
|
|
{
|
|
auto cases = static_cast<ZCC_CaseStmt *>(ast);
|
|
return new FxCaseStatement(ConvertNode(cases->Condition), *ast);
|
|
}
|
|
|
|
case AST_CompoundStmt:
|
|
{
|
|
auto x = new FxCompoundStatement(*ast);
|
|
auto compound = static_cast<ZCC_CompoundStmt *>(ast);
|
|
auto node = compound->Content;
|
|
if (node != nullptr) do
|
|
{
|
|
x->Add(ConvertNode(node));
|
|
node = static_cast<decltype(node)>(node->SiblingNext);
|
|
} while (node != compound->Content);
|
|
return x;
|
|
}
|
|
}
|
|
// 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;
|
|
}
|
|
|
|
|
|
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;
|
|
} |