forked from Mirrors/openclonk
630 lines
14 KiB
C++
630 lines
14 KiB
C++
/*
|
|
* OpenClonk, http://www.openclonk.org
|
|
*
|
|
* Copyright (c) 2001, 2004, 2007-2008 Sven Eberhardt
|
|
* Copyright (c) 2001, 2009 Peter Wortmann
|
|
* Copyright (c) 2006-2009, 2011 Günther Brammer
|
|
* Copyright (c) 2007 Matthes Bender
|
|
* Copyright (c) 2009 Nicolas Hake
|
|
* Copyright (c) 2010 Benjamin Herr
|
|
* Copyright (c) 2010 Martin Plicht
|
|
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
|
|
*
|
|
* Portions might be copyrighted by other authors who have contributed
|
|
* to OpenClonk.
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
* See isc_license.txt for full license and disclaimer.
|
|
*
|
|
* "Clonk" is a registered trademark of Matthes Bender.
|
|
* See clonk_trademark_license.txt for full license.
|
|
*/
|
|
// C4Aul script engine CP conversion
|
|
|
|
#include <C4Include.h>
|
|
#include <C4Aul.h>
|
|
#include <C4AulExec.h>
|
|
#include <C4AulDebug.h>
|
|
|
|
#include <C4Config.h>
|
|
#include <C4Def.h>
|
|
#include <C4Log.h>
|
|
#include <C4Components.h>
|
|
#include <C4Application.h>
|
|
#include <C4LangStringTable.h>
|
|
|
|
C4AulError::C4AulError(): shown(false) {}
|
|
|
|
void C4AulError::show()
|
|
{
|
|
shown = true;
|
|
// simply log error message
|
|
if (sMessage)
|
|
DebugLog(sMessage.getData());
|
|
}
|
|
|
|
C4AulFunc::C4AulFunc(C4AulScript *pOwner, const char *pName, bool bAtEnd):
|
|
MapNext(NULL),
|
|
LinkedTo (NULL),
|
|
OverloadedBy (NULL)
|
|
{
|
|
// reg2list (at end or at the beginning)
|
|
Owner = pOwner;
|
|
if (bAtEnd)
|
|
{
|
|
if ((Prev = Owner->FuncL))
|
|
{
|
|
Prev->Next = this;
|
|
Owner->FuncL = this;
|
|
}
|
|
else
|
|
{
|
|
Owner->Func0 = this;
|
|
Owner->FuncL = this;
|
|
}
|
|
Next = NULL;
|
|
}
|
|
else
|
|
{
|
|
if ((Next = Owner->Func0))
|
|
{
|
|
Next->Prev = this;
|
|
Owner->Func0 = this;
|
|
}
|
|
else
|
|
{
|
|
Owner->Func0 = this;
|
|
Owner->FuncL = this;
|
|
}
|
|
Prev = NULL;
|
|
}
|
|
|
|
// store name
|
|
SCopy(pName, (char *) &Name, C4AUL_MAX_Identifier);
|
|
// add to global lookuptable with this name
|
|
if (GetName())
|
|
Owner->Engine->FuncLookUp.Add(this, bAtEnd);
|
|
}
|
|
|
|
|
|
C4AulFunc::~C4AulFunc()
|
|
{
|
|
// if it's a global: remove the global link!
|
|
if (LinkedTo && Owner)
|
|
if (LinkedTo->Owner == Owner->Engine)
|
|
delete LinkedTo;
|
|
// unlink func
|
|
if (LinkedTo)
|
|
{
|
|
// find prev
|
|
C4AulFunc* pAkt = this;
|
|
while (pAkt->LinkedTo != this) pAkt = pAkt->LinkedTo;
|
|
if (pAkt == LinkedTo)
|
|
pAkt->LinkedTo = NULL;
|
|
else
|
|
pAkt->LinkedTo = LinkedTo;
|
|
LinkedTo = NULL;
|
|
}
|
|
// remove from list
|
|
if (Prev) Prev->Next = Next;
|
|
if (Next) Next->Prev = Prev;
|
|
if (Owner)
|
|
{
|
|
if (Owner->Func0 == this) Owner->Func0 = Next;
|
|
if (Owner->FuncL == this) Owner->FuncL = Prev;
|
|
if (GetName())
|
|
Owner->Engine->FuncLookUp.Remove(this);
|
|
}
|
|
}
|
|
|
|
void C4AulFunc::DestroyLinked()
|
|
{
|
|
// delete all functions linked to this one.
|
|
while (LinkedTo)
|
|
delete LinkedTo;
|
|
}
|
|
|
|
StdStrBuf C4AulScriptFunc::GetFullName()
|
|
{
|
|
// "lost" function?
|
|
StdStrBuf sOwner;
|
|
if (!Owner)
|
|
{
|
|
sOwner.Ref("(unknown) ");
|
|
}
|
|
else if (Owner->Def)
|
|
{
|
|
sOwner.Format("%s::", Owner->Def->id.ToString());
|
|
}
|
|
else if (Owner->Engine == Owner)
|
|
{
|
|
sOwner.Ref("global ");
|
|
}
|
|
else
|
|
{
|
|
sOwner.Ref("game ");
|
|
}
|
|
StdStrBuf sResult;
|
|
sResult.Format("%s%s", sOwner.getData(), GetName());
|
|
return sResult;
|
|
}
|
|
|
|
C4AulScript::C4AulScript()
|
|
{
|
|
// not compiled
|
|
State = ASS_NONE;
|
|
Script = NULL;
|
|
Code.clear();
|
|
LastCode = NULL;
|
|
IncludesResolved = false;
|
|
|
|
// defaults
|
|
Strict = MAXSTRICT;
|
|
Preparsing=Resolving=false;
|
|
Temporary = false;
|
|
LocalNamed.Reset();
|
|
|
|
// prepare lists
|
|
Child0 = ChildL = Prev = Next = NULL;
|
|
Owner = Engine = NULL;
|
|
Func0 = FuncL = NULL;
|
|
// prepare include list
|
|
Includes.clear();
|
|
Appends.clear();
|
|
|
|
stringTable = 0;
|
|
}
|
|
|
|
C4AulScript::~C4AulScript()
|
|
{
|
|
// clear
|
|
Clear();
|
|
// unreg
|
|
Unreg();
|
|
}
|
|
|
|
|
|
void C4AulScript::Unreg()
|
|
{
|
|
// remove from list
|
|
if (Prev) Prev->Next = Next; else if (Owner) Owner->Child0 = Next;
|
|
if (Next) Next->Prev = Prev; else if (Owner) Owner->ChildL = Prev;
|
|
Prev = Next = Owner = NULL;
|
|
}
|
|
|
|
|
|
void C4AulScript::Clear()
|
|
{
|
|
// remove includes
|
|
Includes.clear();
|
|
Appends.clear();
|
|
// delete child scripts + funcs
|
|
while (Child0) // Child0->Unreg();
|
|
if (Child0->Delete()) delete Child0; else Child0->Unreg();
|
|
while (Func0) delete Func0;
|
|
// delete script+code
|
|
Script.Clear();
|
|
ClearCode();
|
|
// reset flags
|
|
State = ASS_NONE;
|
|
}
|
|
|
|
|
|
void C4AulScript::Reg2List(C4AulScriptEngine *pEngine, C4AulScript *pOwner)
|
|
{
|
|
// already regged? (def reloaded)
|
|
if (Owner) return;
|
|
// reg to list
|
|
Engine = pEngine;
|
|
if ((Owner = pOwner))
|
|
{
|
|
if ((Prev = Owner->ChildL))
|
|
Prev->Next = this;
|
|
else
|
|
Owner->Child0 = this;
|
|
Owner->ChildL = this;
|
|
}
|
|
else
|
|
Prev = NULL;
|
|
Next = NULL;
|
|
}
|
|
|
|
|
|
C4AulFunc *C4AulScript::GetOverloadedFunc(C4AulFunc *ByFunc)
|
|
{
|
|
assert(ByFunc);
|
|
// search local list
|
|
C4AulFunc *f = ByFunc;
|
|
if (f) f = f->Prev; else f = FuncL;
|
|
while (f)
|
|
{
|
|
if (SEqual(ByFunc->GetName(), f->GetName())) break;
|
|
f = f->Prev;
|
|
}
|
|
#ifdef _DEBUG
|
|
C4AulFunc * f2 = Engine ? Engine->GetFunc(ByFunc->GetName(), this, ByFunc) : NULL;
|
|
assert (f == f2);
|
|
#endif
|
|
// nothing found? then search owner, if existant
|
|
if (!f && Owner)
|
|
{
|
|
if ((f = Owner->GetFuncRecursive(ByFunc->GetName())))
|
|
// just found the global link?
|
|
if (ByFunc && f->LinkedTo == ByFunc)
|
|
f = Owner->GetOverloadedFunc(f);
|
|
}
|
|
// return found fn
|
|
return f;
|
|
}
|
|
|
|
C4AulFunc *C4AulScript::GetFuncRecursive(const char *pIdtf)
|
|
{
|
|
// search local list
|
|
C4AulFunc *f = GetFunc(pIdtf);
|
|
if (f) return f;
|
|
// nothing found? then search owner, if existant
|
|
else if (Owner) return Owner->GetFuncRecursive(pIdtf);
|
|
return NULL;
|
|
}
|
|
|
|
C4AulFunc *C4AulScript::GetFunc(const char *pIdtf)
|
|
{
|
|
C4AulFunc * f = Engine ? Engine->GetFunc(pIdtf, this, NULL) : NULL;
|
|
#if 0
|
|
// search func list
|
|
C4AulFunc *f2 = FuncL;
|
|
while (f2)
|
|
{
|
|
if (SEqual(pIdtf, f2->Name)) break;
|
|
f2 = f2->Prev;
|
|
}
|
|
assert (f == f2);
|
|
#endif
|
|
return f;
|
|
}
|
|
|
|
|
|
C4AulScriptFunc *C4AulScript::GetSFuncWarn(const char *pIdtf, C4AulAccess AccNeeded, const char *WarnStr)
|
|
{
|
|
// no identifier
|
|
if (!pIdtf || !pIdtf[0]) return NULL;
|
|
// get func?
|
|
C4AulScriptFunc *pFn = GetSFunc(pIdtf, AccNeeded, true);
|
|
if (!pFn)
|
|
Warn(FormatString("Error getting %s function '%s'", WarnStr, pIdtf).getData(), NULL);
|
|
return pFn;
|
|
}
|
|
|
|
C4AulScriptFunc *C4AulScript::GetSFunc(const char *pIdtf, C4AulAccess AccNeeded, bool fFailsafe)
|
|
{
|
|
// failsafe call
|
|
if (*pIdtf=='~') { fFailsafe=true; pIdtf++; }
|
|
|
|
// get function reference from table
|
|
C4AulScriptFunc *pFn = GetSFunc(pIdtf);
|
|
|
|
// undefined function
|
|
if (!pFn)
|
|
{
|
|
// not failsafe?
|
|
if (!fFailsafe)
|
|
{
|
|
// show error
|
|
C4AulParseError err(this, "Undefined function: ", pIdtf);
|
|
err.show();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// check access
|
|
if (pFn->Access < AccNeeded)
|
|
{
|
|
// no access? show error
|
|
C4AulParseError err(this, "insufficient access level");
|
|
err.show();
|
|
// don't even break in strict execution, because the caller might be non-strict
|
|
//if (Strict) return NULL;
|
|
}
|
|
|
|
// return found function
|
|
return pFn;
|
|
}
|
|
|
|
C4AulScriptFunc *C4AulScript::GetSFunc(const char *pIdtf)
|
|
{
|
|
// get func by name; return script func
|
|
if (!pIdtf) return NULL;
|
|
if (!pIdtf[0]) return NULL;
|
|
if (pIdtf[0] == '~') pIdtf++;
|
|
C4AulFunc *f = GetFunc(pIdtf);
|
|
if (!f) return NULL;
|
|
return f->SFunc();
|
|
}
|
|
|
|
std::string C4AulScript::Translate(const std::string &text) const
|
|
{
|
|
const C4AulScript *cursor = this;
|
|
while (cursor)
|
|
{
|
|
try
|
|
{
|
|
if (cursor->stringTable)
|
|
return cursor->stringTable->Translate(text);
|
|
}
|
|
catch (C4LangStringTable::NoSuchTranslation &)
|
|
{
|
|
// Ignore, soldier on
|
|
}
|
|
// Walk tree structure upwards
|
|
cursor = cursor->Owner;
|
|
}
|
|
throw C4LangStringTable::NoSuchTranslation(text);
|
|
}
|
|
|
|
void C4AulScriptFunc::CopyBody(C4AulScriptFunc &FromFunc)
|
|
{
|
|
// copy some members, that are set before linking
|
|
Access = FromFunc.Access;
|
|
Script = FromFunc.Script;
|
|
VarNamed = FromFunc.VarNamed;
|
|
ParNamed = FromFunc.ParNamed;
|
|
ParCount = FromFunc.ParCount;
|
|
pOrgScript = FromFunc.pOrgScript;
|
|
for (int i = 0; i < C4AUL_MAX_Par; i++)
|
|
ParType[i] = FromFunc.ParType[i];
|
|
}
|
|
|
|
|
|
void C4AulScript::AddFunc(const char *pIdtf, C4ScriptFnDef* Def)
|
|
{
|
|
// create def func
|
|
new C4AulDefFunc(this, pIdtf, Def);
|
|
}
|
|
|
|
|
|
/*--- C4AulScriptEngine ---*/
|
|
|
|
C4AulScriptEngine::C4AulScriptEngine():
|
|
warnCnt(0), errCnt(0), nonStrictCnt(0), lineCnt(0)
|
|
{
|
|
// /me r b engine
|
|
Engine = this;
|
|
ScriptName.Ref(C4CFN_System);
|
|
Strict = MAXSTRICT;
|
|
|
|
GlobalNamedNames.Reset();
|
|
GlobalNamed.Reset();
|
|
GlobalNamed.SetNameList(&GlobalNamedNames);
|
|
GlobalConstNames.Reset();
|
|
GlobalConsts.Reset();
|
|
GlobalConsts.SetNameList(&GlobalConstNames);
|
|
}
|
|
|
|
|
|
C4AulScriptEngine::~C4AulScriptEngine() { Clear(); }
|
|
|
|
void C4AulScriptEngine::Clear()
|
|
{
|
|
#ifndef NOAULDEBUG
|
|
// stop debugger
|
|
delete C4AulDebug::GetDebugger();
|
|
#endif
|
|
// clear inherited
|
|
C4AulScript::Clear();
|
|
// clear own stuff
|
|
// reset values
|
|
warnCnt = errCnt = nonStrictCnt = lineCnt = 0;
|
|
// resetting name lists will reset all data lists, too
|
|
// except not...
|
|
GlobalNamedNames.Reset();
|
|
GlobalConstNames.Reset();
|
|
GlobalConsts.Reset();
|
|
GlobalConsts.SetNameList(&GlobalConstNames);
|
|
GlobalNamed.Reset();
|
|
GlobalNamed.SetNameList(&GlobalNamedNames);
|
|
}
|
|
|
|
|
|
void C4AulScriptEngine::UnLink()
|
|
{
|
|
// unlink scripts
|
|
C4AulScript::UnLink();
|
|
// Do not clear global variables and constants, because they are registered by the
|
|
// preparser. Note that keeping those fields means that you cannot delete a global
|
|
// variable or constant at runtime by removing it from the script.
|
|
//GlobalNamedNames.Reset();
|
|
//GlobalConstNames.Reset();
|
|
}
|
|
|
|
|
|
void C4AulScriptEngine::RegisterGlobalConstant(const char *szName, const C4Value &rValue)
|
|
{
|
|
// Register name and set value.
|
|
// AddName returns the index of existing element if the name is assigned already.
|
|
// That is OK, since it will only change the value of the global ("overload" it).
|
|
// A warning would be nice here. However, this warning would show up whenever a script
|
|
// containing globals constants is recompiled.
|
|
GlobalConsts[GlobalConstNames.AddName(szName)] = rValue;
|
|
}
|
|
|
|
bool C4AulScriptEngine::GetGlobalConstant(const char *szName, C4Value *pTargetValue)
|
|
{
|
|
// get index of global by name
|
|
int32_t iConstIndex = GlobalConstNames.GetItemNr(szName);
|
|
// not found?
|
|
if (iConstIndex<0) return false;
|
|
// if it's found, assign the value if desired
|
|
if (pTargetValue) *pTargetValue = GlobalConsts[iConstIndex];
|
|
// constant exists
|
|
return true;
|
|
}
|
|
|
|
bool C4AulScriptEngine::Denumerate(C4ValueNumbers * numbers)
|
|
{
|
|
GlobalNamed.Denumerate(numbers);
|
|
// runtime data only: don't denumerate consts
|
|
return true;
|
|
}
|
|
|
|
void C4AulScriptEngine::CompileFunc(StdCompiler *pComp, C4ValueNumbers * numbers)
|
|
{
|
|
C4ValueMapData GlobalNamedDefault;
|
|
GlobalNamedDefault.SetNameList(&GlobalNamedNames);
|
|
pComp->Value(mkNamingAdapt(mkParAdapt(GlobalNamed, numbers), "StaticVariables", GlobalNamedDefault));
|
|
}
|
|
|
|
std::list<const char*> C4AulScriptEngine::GetFunctionNames(C4AulScript * script)
|
|
{
|
|
std::list<const char*> functions;
|
|
for (C4AulFunc *pFn = Func0; pFn; pFn = pFn->Next)
|
|
{
|
|
if (pFn->GetPublic())
|
|
{
|
|
functions.push_back(pFn->GetName());
|
|
}
|
|
}
|
|
// Add object or scenario script functions
|
|
if (script)
|
|
{
|
|
bool divider = false;
|
|
C4AulFunc *f = script->FuncL;
|
|
C4AulScriptFunc *pRef;
|
|
// Scan all functions
|
|
while (f)
|
|
{
|
|
if ((pRef = f->SFunc()))
|
|
{
|
|
// Public functions only
|
|
if (pRef->Access == AA_PUBLIC)
|
|
{
|
|
// Insert divider if necessary
|
|
if (!divider)
|
|
functions.push_back(0);
|
|
divider = true;
|
|
// Add function
|
|
functions.push_back(pRef->GetName());
|
|
}
|
|
}
|
|
f = f->Prev;
|
|
}
|
|
}
|
|
return functions;
|
|
}
|
|
|
|
/*--- C4AulFuncMap ---*/
|
|
static const size_t CapacityInc = 1024;
|
|
|
|
C4AulFuncMap::C4AulFuncMap(): Funcs(new C4AulFunc*[CapacityInc]), FuncCnt(0), Capacity(CapacityInc)
|
|
{
|
|
memset(Funcs, 0, sizeof (C4AulFunc *) * Capacity);
|
|
}
|
|
|
|
C4AulFuncMap::~C4AulFuncMap()
|
|
{
|
|
delete[] Funcs;
|
|
}
|
|
|
|
unsigned int C4AulFuncMap::Hash(const char * name)
|
|
{
|
|
// Fowler/Noll/Vo hash
|
|
unsigned int h = 2166136261u;
|
|
while (*name)
|
|
h = (h ^ *(name++)) * 16777619;
|
|
return h;
|
|
}
|
|
|
|
C4AulFunc * C4AulFuncMap::GetFirstFunc(const char * Name)
|
|
{
|
|
if (!Name) return NULL;
|
|
C4AulFunc * Func = Funcs[Hash(Name) % Capacity];
|
|
while (Func && !SEqual(Name, Func->GetName()))
|
|
Func = Func->MapNext;
|
|
return Func;
|
|
}
|
|
|
|
C4AulFunc * C4AulFuncMap::GetNextSNFunc(const C4AulFunc * After)
|
|
{
|
|
C4AulFunc * Func = After->MapNext;
|
|
while (Func && !SEqual(After->GetName(), Func->GetName()))
|
|
Func = Func->MapNext;
|
|
return Func;
|
|
}
|
|
|
|
C4AulFunc * C4AulFuncMap::GetFunc(const char * Name, const C4AulScript * Owner, const C4AulFunc * After)
|
|
{
|
|
if (!Name) return NULL;
|
|
C4AulFunc * Func = Funcs[Hash(Name) % Capacity];
|
|
if (After)
|
|
{
|
|
while (Func && Func != After)
|
|
Func = Func->MapNext;
|
|
if (Func)
|
|
Func = Func->MapNext;
|
|
}
|
|
while (Func && (Func->Owner != Owner || !SEqual(Name, Func->GetName())))
|
|
Func = Func->MapNext;
|
|
return Func;
|
|
}
|
|
|
|
void C4AulFuncMap::Add(C4AulFunc * func, bool bAtStart)
|
|
{
|
|
if (++FuncCnt > Capacity)
|
|
{
|
|
int NCapacity = Capacity + CapacityInc;
|
|
C4AulFunc ** NFuncs = new C4AulFunc*[NCapacity];
|
|
memset(NFuncs, 0, sizeof (C4AulFunc *) * NCapacity);
|
|
for (int i = 0; i < Capacity; ++i)
|
|
{
|
|
while (Funcs[i])
|
|
{
|
|
// Get a pointer to the bucket
|
|
C4AulFunc ** pNFunc = &(NFuncs[Hash(Funcs[i]->GetName()) % NCapacity]);
|
|
// get a pointer to the end of the linked list
|
|
while (*pNFunc) pNFunc = &((*pNFunc)->MapNext);
|
|
// Move the func over
|
|
*pNFunc = Funcs[i];
|
|
// proceed with the next list member
|
|
Funcs[i] = Funcs[i]->MapNext;
|
|
// Terminate the linked list
|
|
(*pNFunc)->MapNext = 0;
|
|
}
|
|
}
|
|
Capacity = NCapacity;
|
|
delete [] Funcs;
|
|
Funcs = NFuncs;
|
|
}
|
|
// Get a pointer to the bucket
|
|
C4AulFunc ** pFunc = &(Funcs[Hash(func->GetName()) % Capacity]);
|
|
if (bAtStart)
|
|
{
|
|
// move the current first to the second position
|
|
func->MapNext = *pFunc;
|
|
}
|
|
else
|
|
{
|
|
// get a pointer to the end of the linked list
|
|
while (*pFunc)
|
|
{
|
|
pFunc = &((*pFunc)->MapNext);
|
|
}
|
|
}
|
|
// Add the func
|
|
*pFunc = func;
|
|
}
|
|
|
|
void C4AulFuncMap::Remove(C4AulFunc * func)
|
|
{
|
|
C4AulFunc ** pFunc = &Funcs[Hash(func->GetName()) % Capacity];
|
|
while (*pFunc != func)
|
|
{
|
|
pFunc = &((*pFunc)->MapNext);
|
|
assert(*pFunc); // crash on remove of a not contained func
|
|
}
|
|
*pFunc = (*pFunc)->MapNext;
|
|
--FuncCnt;
|
|
}
|
|
|
|
C4AulScriptEngine ScriptEngine;
|