I'm writing an interpreter for a scripting language which allows objects to:
- Have an alias
- Be referenced using a wildcard
- Contain child objects
- Combination of all three above
For example:
CreateWindow("win", ...);
CreateName("win/texes1", ...);
CreateName("win/texes2", ...);
CreateAlias("win/texes1", "wintexes");
CreateAlias("win/texes2", "wintexes2");
CreateTexture("@wintexes/tex1", ...);
CreateTexture("@wintexes/tex2", ...);
Delete("@wintexes/*"); // Deletes textures
Delete("@wintexes*"); // Deletes names
Delete("win");
Crazy, I know. It wasn't me who came up with it though.
In order to call the function on all objects intended to be affected by it, I came up with this code:
class ObjectHolder_t : private Holder<Object>
{
public:
virtual ~ObjectHolder_t()
{
}
Object* Read(const string& Handle)
{
string Leftover = Handle;
string ObjHandle = ExtractObjHandle(Leftover);
if (Leftover.empty())
return Holder::Read(ObjHandle);
return GetHolder(ObjHandle)->Read(Leftover);
}
void Write(const string& Handle, Object* pObject)
{
string Leftover = Handle;
string ObjHandle = ExtractObjHandle(Leftover);
if (Leftover.empty())
Holder::Write(ObjHandle, pObject);
else
GetHolder(ObjHandle)->Write(Leftover, pObject);
}
template <class F>
void Execute(const string& Handle, F Func)
{
string Leftover = Handle;
string ObjHandle = ExtractObjHandle(Leftover);
if (ObjHandle.back() == '*')
ObjHandle.front() == '@' ? WildcardAlias(Leftover, ObjHandle, Func) : WildcardCache(Leftover, ObjHandle, Func);
else
Leftover.empty() ? Func(&Cache.find(ObjHandle)->second) : GetHolder(ObjHandle)->Execute(Leftover, Func);
}
void WriteAlias(const string& Handle, const string& Alias)
{
Aliases[Alias] = Handle;
}
private:
template <class F>
void ExecuteSafe(const string& HolderHandle, const string& Handle, F Func)
{
if (ObjectHolder_t* pHolder = GetHolder(HolderHandle))
pHolder->Execute(Handle, Func);
}
template <class F>
void WildcardAlias(const string& Leftover, const string& ObjHandle, F Func)
{
std::regex Regex(Regexify(ObjHandle.substr(1)));
for (auto i = Aliases.begin(); i != Aliases.end(); ++i)
if (std::regex_match(i->first, Regex))
Leftover.empty() ? Execute(i->second, Func) : ExecuteSafe(i->second, Leftover, Func);
}
template <class F>
void WildcardCache(const string& Leftover, const string& ObjHandle, F Func)
{
std::regex Regex(Regexify(ObjHandle));
for (auto i = Cache.begin(); i != Cache.end(); ++i)
if (std::regex_match(i->first, Regex))
Leftover.empty() ? Func(&i->second) : ExecuteSafe(i->first, Leftover, Func);
}
string Regexify(const string& Wildcard)
{
return string("^" + Wildcard.substr(0, Wildcard.size() - 1) + ".*");
}
string ExtractObjHandle(string& Handle)
{
// Name
string ObjHandle;
size_t Index = Handle.find('/');
if (Index != string::npos)
{
ObjHandle = Handle.substr(0, Index);
Handle = Handle.substr(Index + 1);
}
else
{
ObjHandle = Handle;
Handle.clear();
}
// Alias
if (ObjHandle.front() == '@' && ObjHandle.back() != '*')
{
Handle = Aliases[ObjHandle.substr(1)] + "/" + Handle;
ObjHandle = ExtractObjHandle(Handle);
}
return ObjHandle;
}
ObjectHolder_t* GetHolder(const string& Handle)
{
return (ObjectHolder_t*)(Holder::Read(Handle));
}
map<string, string> Aliases;
};
Variable name Cache
is a bit misleading here, but that's what I get for reusing Holder<T>
which was intended for another purpose. Cache
is actually std::map<std::string, Object*>
.
struct MapDeleter
{
template <class T> void operator() (T Data) { delete Data.second; }
};
template <class T>
struct Holder
{
~Holder()
{
for_each(Cache.begin(), Cache.end(), MapDeleter());
}
T* Read(const string& Path)
{
auto iter = Cache.find(Path);
if (iter != Cache.end())
return iter->second;
return nullptr;
}
void Write(const string& Path, T* Data)
{
Cache[Path] = Data;
}
map<string, T*> Cache;
};
The code is used like this:
void NSBInterpreter::Move()
{
string Handle = PopString();
int32_t Time = PopInt();
int32_t X = PopInt();
int32_t Y = PopInt();
/*int32_t Tempo = */PopInt();
bool Wait = PopBool();
ObjectHolder.Execute(Handle, [Time, X, Y] (Object** ppObject)
{
if (Texture* pTexture = dynamic_cast<Texture*>(*ppObject))
pTexture->Move(Time, X, Y);
});
if (Wait)
pContext->Wait(Time);
}
My concern with this code is that it is very complex and spaghetti recursive (although recursion isn't very deep so it won't crash) and I will probably forget how exactly it works by the time it needs fixing. Any thoughts on improving this?
CreateWindow
,CreateName
, etc.? \$\endgroup\$ObjectHolder.Write(Handle, pWindow);
and SetAlias isObjectHolder.WriteAlias(Handle, PopString());
\$\endgroup\$ObjHandle.back()=='*'
. It's illegal ifObjHandle
is empty and it appears to be accessed at the entry point of this module. Even if there's protection elsewhere I would always recommend guarding the entry point. The behavior of accessing an empty string could be catastrophic. \$\endgroup\$