vkdoom_m/src/scripting/zscript/zcc_compile.cpp
Christoph Oelckers b1a83bfd26 - started with cleanup and separation of DECORATE code.
* everything related to scripting is now placed in a subdirectory 'scripting', which itself is separated into DECORATE, ZSCRIPT, the VM and code generation.
 * a few items have been moved to different headers so that the DECORATE parser definitions can mostly be kept local. The only exception at the moment is the flags interface on which 3 source files depend.
2016-10-12 19:22:33 +02:00

2243 lines
63 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 "gdtoa.h"
#include "codegeneration/thingdef_exp.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);
// 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), ErrorCount(0), WarnCount(0)
{
// 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);
WarnCount++;
}
//==========================================================================
//
// 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);
ErrorCount++;
}
//==========================================================================
//
// 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 ErrorCount;
}
//==========================================================================
//
// 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)
{
Error(exp, "%s: non-constant parameter", prop->name);
}
conv.s = nullptr;
pref.s = nullptr;
pref.i = -1;
switch ((*p) & 223)
{
case 'I':
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.
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++;
else if (*p == 0)
{
if (exp != property->Values)
{
Error(property, "Too many values for '%s'", prop->name);
}
break;
}
else if (exp == property->Values)
{
if (*p < 'a')
{
Error(property, "Insufficient parameters for %s", prop->name);
}
break;
}
}
}
// call the handler
FScriptPosition::ErrorCounter = 0;
try
{
prop->Handler(defaults, bag.Info, bag, &params[0]);
}
catch (CRecoverableError &error)
{
Error(property, "%s", error.GetMessage());
}
ErrorCount += FScriptPosition::ErrorCounter;
}
//==========================================================================
//
// 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 = Wads.CheckNumForFullName(*c->cls->SourceName, true);
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;
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);
}
}
}
}
}
//==========================================================================
//
// Parses the functions list
//
//==========================================================================
void ZCCCompiler::InitFunctions()
{
TArray<PType *> rets(1);
TArray<PType *> args;
TArray<DWORD> argflags;
TArray<ENamedName> 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);
// 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, c->Type(), varflags);
// Give names to the implicit parameters.
// Note that 'self' is the second argument on action functions, because this is the one referring to the owning class.
if (varflags & VARF_Action)
{
argnames.Push(NAME_caller);
argnames.Push(NAME_self);
argnames.Push(NAME_stateinfo);
}
else if (varflags & VARF_Method)
{
argnames.Push(NAME_self);
}
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(f->Name);
sym->AddVariant(NewPrototype(rets, args), argflags, afd == nullptr? nullptr : *(afd->VMPointer));
sym->Flags = 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);
PFunction *afd = dyn_cast<PFunction>(cls->Symbols.FindSymbol(id->Identifier, true));
if (afd != nullptr)
{
if (fc->Parameters == nullptr && (afd->Flags & VARF_Action))
{
// This is the simple case which doesn't require work on the tree.
return new FxVMFunctionCall(afd, nullptr, *af, true);
}
else
{
// need to generate a function from the information.
}
}
else
{
Error(af, "%s: action function not found in %s", FName(id->Identifier).GetChars(), cls->TypeName.GetChars());
return nullptr;
}
}
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;
for (auto s : c->States)
{
auto st = s->Body;
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
int count = statedef.AddStates(&state, sl->Frames->GetChars());
if (count < 0)
{
Error(sl, "Invalid frame character string '%s'", sl->Frames->GetChars());
count = -count;
}
if (sl->Action != nullptr)
{
auto code = SetupActionFunction(static_cast<PClassActor *>(c->Type()), sl->Action);
if (code != nullptr)
{
auto tcall = new FStateTempCall;
tcall->Code = code;
tcall->ActorClass = static_cast<PClassActor *>(c->Type());
tcall->FirstState = statedef.GetStateCount() - count;
tcall->NumStates = count;
StateTempCalls.Push(tcall);
}
}
break;
}
case AST_StateGoto:
{
auto sg = static_cast<ZCC_StateGoto *>(st);
statename = "";
if (sg->Qualifier != nullptr)
{
statename << sg->Qualifier->Id << "::";
}
auto part = sg->Label;
do
{
statename << 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);
}
}
}