mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-12-15 15:01:42 +00:00
55f6661c87
For the random functions this class only handles the default-RNG version. The one with an explicit RNG needs to be done separately because the parser produces different output for them.
2355 lines
No EOL
67 KiB
C++
2355 lines
No EOL
67 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 "actor.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_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 DEFINING_CONST ((PSymbolConst *)(void *)1)
|
|
|
|
//==========================================================================
|
|
//
|
|
// ZCCCompiler :: ProcessClass
|
|
//
|
|
//==========================================================================
|
|
|
|
void ZCCCompiler::ProcessClass(ZCC_Class *cnode, PSymbolTreeNode *treenode)
|
|
{
|
|
Classes.Push(new ZCC_ClassWork(static_cast<ZCC_Class *>(cnode), treenode));
|
|
auto cls = Classes.Last();
|
|
|
|
auto node = cnode->Body;
|
|
PSymbolTreeNode *childnode;
|
|
ZCC_Enum *enumType = nullptr;
|
|
|
|
FString name;
|
|
name << "nodes - " << FName(cnode->NodeName);
|
|
cls->TreeNodes.SetName(name);
|
|
|
|
if (cnode->Replaces != nullptr && !static_cast<PClassActor *>(cnode->Type)->SetReplacement(cnode->Replaces->Id))
|
|
{
|
|
Warn(cnode, "Replaced type '%s' not found for %s", FName(cnode->Replaces->Id).GetChars(), cnode->Type->TypeName.GetChars());
|
|
}
|
|
|
|
// 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)
|
|
: Outer(_outer), GlobalTreeNodes(&_symbols), OutputSymbols(&_outsymbols), AST(ast)
|
|
{
|
|
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:
|
|
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;
|
|
}
|
|
else
|
|
{
|
|
// 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->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
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// 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);
|
|
|
|
def->Symbol = DEFINING_CONST; // avoid recursion
|
|
ZCC_Expression *val = Simplify(def->Value, sym);
|
|
def->Value = val;
|
|
if (def->Symbol == DEFINING_CONST) def->Symbol = nullptr;
|
|
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)
|
|
{
|
|
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 = Simplify(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 = Simplify(binary->Left, sym);
|
|
binary->Right = Simplify(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;
|
|
|
|
dotop->Left = Simplify(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;
|
|
|
|
callop->Function = Simplify(callop->Function, sym);
|
|
parm = callop->Parameters;
|
|
if (parm != NULL)
|
|
{
|
|
do
|
|
{
|
|
parmcount++;
|
|
assert(parm->NodeType == AST_FuncParm);
|
|
parm->Value = Simplify(parm->Value, sym);
|
|
parm = static_cast<ZCC_FuncParm *>(parm->SiblingNext);
|
|
}
|
|
while (parm != callop->Parameters);
|
|
}
|
|
// 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 type 1 to type 2");
|
|
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
|
|
{
|
|
// 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);
|
|
|
|
// 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));
|
|
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)
|
|
{
|
|
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:
|
|
return TypeSInt8;
|
|
|
|
case ZCC_UInt8:
|
|
return TypeUInt8;
|
|
|
|
case ZCC_SInt16:
|
|
return TypeSInt16;
|
|
|
|
case ZCC_UInt16:
|
|
return TypeUInt16;
|
|
|
|
case ZCC_SInt32:
|
|
case ZCC_IntAuto: // todo: for enums, autoselect appropriately sized int
|
|
return TypeSInt32;
|
|
|
|
case ZCC_UInt32:
|
|
return TypeUInt32;
|
|
|
|
case ZCC_Bool:
|
|
return TypeBool;
|
|
|
|
// 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:
|
|
return TypeFloat64;
|
|
|
|
case ZCC_String:
|
|
return TypeString;
|
|
|
|
case ZCC_Name:
|
|
return TypeName;
|
|
|
|
case ZCC_Vector2:
|
|
return TypeVector2;
|
|
|
|
case ZCC_Vector3:
|
|
return TypeVector3;
|
|
|
|
case ZCC_Vector4:
|
|
// This has almost no use, so we really shouldn't bother.
|
|
Error(field, "vector<4> not implemented for %s", name.GetChars());
|
|
return TypeError;
|
|
|
|
case ZCC_State:
|
|
return TypeState;
|
|
|
|
case ZCC_Color:
|
|
return TypeColor;
|
|
|
|
case ZCC_Sound:
|
|
return TypeSound;
|
|
|
|
case ZCC_UserType:
|
|
return ResolveUserType(btype, &outertype->Symbols);
|
|
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);
|
|
return NewMap(DetermineType(outertype, field, name, mtype->KeyType, false), DetermineType(outertype, field, name, mtype->ValueType, false));
|
|
}
|
|
break;
|
|
|
|
case AST_DynArrayType:
|
|
if (allowarraytypes)
|
|
{
|
|
Error(field, "%s: Dynamic array types not implemented yet", name.GetChars());
|
|
auto atype = static_cast<ZCC_DynArrayType *>(ztype);
|
|
return NewDynArray(DetermineType(outertype, field, name, atype->ElementType, false));
|
|
}
|
|
break;
|
|
|
|
case AST_ClassType:
|
|
{
|
|
auto ctype = static_cast<ZCC_ClassType *>(ztype);
|
|
if (ctype->Restriction == nullptr)
|
|
{
|
|
return 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;
|
|
}
|
|
return NewClassPointer(static_cast<PClass *>(typesym->Type));
|
|
}
|
|
}
|
|
}
|
|
return TypeError;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// 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 type = static_cast<PSymbolType *>(sym)->Type;
|
|
if (type->IsKindOf(RUNTIME_CLASS(PEnum)))
|
|
{
|
|
return TypeSInt32; // hack this to an integer until we can resolve the enum mess.
|
|
}
|
|
return type;
|
|
}
|
|
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);
|
|
ZCC_Expression *val;
|
|
do
|
|
{
|
|
val = Simplify(arraysize, sym);
|
|
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 0;
|
|
}
|
|
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 0;
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// 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); // 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 numerical value.
|
|
if (!GetString(exp, true))
|
|
{
|
|
// apply this expression to the next argument on the list.
|
|
params.Push(conv);
|
|
params[0].i++;
|
|
p++;
|
|
continue;
|
|
}
|
|
// fall through
|
|
|
|
case 'C': // this parser accepts colors only in string form.
|
|
pref.i = 1;
|
|
case 'S':
|
|
conv.s = GetString(exp);
|
|
break;
|
|
|
|
case 'T': // a filtered string
|
|
conv.s = strings[strings.Reserve(1)] = strbin1(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);
|
|
} 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);
|
|
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)
|
|
{
|
|
// 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->structoffset == -1)
|
|
{
|
|
Warn(flg, "Deprecated flag '%s%s%s' used", n1, n2 ? "." : "", n2 ? n2 : "");
|
|
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());
|
|
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.fromZScript = true;
|
|
bag.CurrentState = 0;
|
|
bag.Lumpnum = c->cls->SourceLump;
|
|
bag.DropItemList = nullptr;
|
|
bag.ScriptPosition.StrictErrors = true;
|
|
// 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<FName> argnames;
|
|
|
|
for (auto c : Classes)
|
|
{
|
|
for (auto f : c->Functions)
|
|
{
|
|
rets.Clear();
|
|
args.Clear();
|
|
argflags.Clear();
|
|
// 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);
|
|
if (type->IsKindOf(RUNTIME_CLASS(PStruct)))
|
|
{
|
|
// 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));
|
|
f->Flags &= notallowed;
|
|
}
|
|
uint32_t varflags = VARF_Method;
|
|
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; // Action implies Final.
|
|
if (f->Flags & ZCC_Static) varflags = (varflags & ~VARF_Method) | VARF_Final; // 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());
|
|
}
|
|
}
|
|
SetImplicitArgs(&args, &argflags, &argnames, c->Type(), varflags);
|
|
auto p = f->Params;
|
|
if (p != nullptr)
|
|
{
|
|
do
|
|
{
|
|
if (p->Type != nullptr)
|
|
{
|
|
auto type = DetermineType(c->Type(), p, f->Name, p->Type, false);
|
|
int flags;
|
|
if (p->Flags & ZCC_In) flags |= VARF_In;
|
|
if (p->Flags & ZCC_Out) flags |= VARF_Out;
|
|
if (p->Default != nullptr)
|
|
{
|
|
auto val = Simplify(p->Default, &c->Type()->Symbols);
|
|
flags |= VARF_Optional;
|
|
if (val->Operation != PEX_ConstValue)
|
|
{
|
|
Error(c->cls, "Default parameter %s is not constant in %s", FName(p->Name).GetChars(), FName(f->Name).GetChars());
|
|
}
|
|
// Todo: Store and handle the default value (native functions will discard it anyway but for scripted ones this should be done decently.)
|
|
}
|
|
// TBD: disallow certain types? For now, let everything pass that isn't an array.
|
|
args.Push(type);
|
|
argflags.Push(flags);
|
|
}
|
|
else
|
|
{
|
|
args.Push(nullptr);
|
|
argflags.Push(0);
|
|
}
|
|
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);
|
|
|
|
// todo: Check inheritance.
|
|
// todo: Process function bodies.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// 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(PClassActor *cls, ZCC_TreeNode *af)
|
|
{
|
|
// We have 3 cases to consider here:
|
|
// 1. An action function without parameters. This can be called directly
|
|
// 2. An action functon with parameters or a non-action function. 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_Action))
|
|
{
|
|
// We can use this function directly without wrapping it in a caller.
|
|
return new FxVMFunctionCall(afd, nullptr, *af, true);
|
|
}
|
|
}
|
|
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.
|
|
}
|
|
}
|
|
}
|
|
ConvertClass = cls;
|
|
return ConvertAST(af);
|
|
|
|
//Error(af, "Complex action functions not supported yet.");
|
|
//return nullptr;
|
|
|
|
/*
|
|
bool hasfinalret;
|
|
tcall->Code = ParseActions(sc, state, statestring, bag, hasfinalret);
|
|
if (!hasfinalret && tcall->Code != nullptr)
|
|
{
|
|
static_cast<FxSequence *>(tcall->Code)->Add(new FxReturnStatement(nullptr, sc));
|
|
}
|
|
*/
|
|
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Compile the states
|
|
//
|
|
//==========================================================================
|
|
|
|
void ZCCCompiler::CompileStates()
|
|
{
|
|
for (auto c : Classes)
|
|
{
|
|
if (!c->Type()->IsDescendantOf(RUNTIME_CLASS(AActor)))
|
|
{
|
|
Error(c->cls, "%s: States can only be defined for actors.", c->Type()->TypeName.GetChars());
|
|
continue;
|
|
}
|
|
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));
|
|
|
|
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
|
|
// and there is little point fixing it because it is essentially useless outside of resolving constants.
|
|
if (CheckRandom(sl->Duration))
|
|
{
|
|
auto func = static_cast<ZCC_ExprFuncCall *>(Simplify(sl->Duration, &c->Type()->Symbols));
|
|
if (func->Parameters == func->Parameters->SiblingNext || func->Parameters != func->Parameters->SiblingNext->SiblingNext)
|
|
{
|
|
Error(sl, "Random duration requires exactly 2 parameters");
|
|
}
|
|
int v1 = GetInt(func->Parameters->Value);
|
|
int v2 = GetInt(static_cast<ZCC_FuncParm *>(func->Parameters->SiblingNext)->Value);
|
|
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);
|
|
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));
|
|
auto o2 = static_cast<ZCC_Expression *>(Simplify(static_cast<ZCC_Expression *>(o1->SiblingNext), &c->Type()->Symbols));
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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);
|
|
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(ZCC_TreeNode *ast)
|
|
{
|
|
// FxReturnStatement will have to be done more intelligently, of course.
|
|
return new FxReturnStatement(ConvertNode(ast), *ast);
|
|
}
|
|
|
|
FxExpression *ZCCCompiler::ConvertNode(ZCC_TreeNode *ast)
|
|
{
|
|
// 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);
|
|
assert(fcall->Function->NodeType == AST_ExprID); // of course this cannot remain. Right now nothing more complex can come along but later this will have to be decomposed into 'self' and the actual function name.
|
|
auto fname = static_cast<ZCC_ExprID *>(fcall->Function)->Identifier;
|
|
return new FxFunctionCall(fname, ConvertNodeList(fcall->Parameters), *ast);
|
|
//return ConvertFunctionCall(fcall->Function, ConvertNodeList(fcall->Parameters), ConvertClass, *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(PFloat)))
|
|
{
|
|
return new FxConstant(cnst->DoubleVal, *ast);
|
|
}
|
|
else if (cnst->Type->IsA(RUNTIME_CLASS(PString)))
|
|
{
|
|
return new FxConstant(*cnst->StringVal, *ast);
|
|
}
|
|
else
|
|
{
|
|
// can there be other types?
|
|
Error(cnst, "Unknown constant type");
|
|
return new FxConstant(0, *ast);
|
|
}
|
|
}
|
|
|
|
default:
|
|
// 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(ZCC_TreeNode *head)
|
|
{
|
|
FArgumentList *list = new FArgumentList;
|
|
auto node = head;
|
|
do
|
|
{
|
|
list->Push(ConvertNode(node));
|
|
node = node->SiblingNext;
|
|
} while (node != head);
|
|
return list;
|
|
} |