I just recently forked my script interfacing code out into it's own library so I could use it in multiple projects. It binds three seperate things together - AngelScript, my Preprocessor, and the Auto Binding tool I posted here some time ago. I'm planning on releasing it as whole instead sticking the auto binder up alone. Take a look. WL - I especially want to know if I'm using Angelscript in some wrong/inefficient way.
ScriptManager.h
#ifndef JM_SERVER_SCRIPTMANAGER_H
#define JM_SERVER_SCRIPTMANAGER_H
#include <string>
#include <Preprocessor.h>
namespace ScriptInterface
{
bool Initialize(Preprocessor::FileSource*,Preprocessor::OutStream*);
void Destroy();
void DiscardScript(const std::string&);
bool ExecuteFunction(const std::string& script, const std::string& func);
bool ExecuteString(const std::string& script);
};
#endif
ScriptManager.cpp
#include "../scriptinterface.h"
#include <angelscript.h>
#include <iostream>
#include <system/vfs.h>
#include <preprocessor/preprocess.h>
#include <cassert>
#include <sstream>
#ifdef JM_SCRIPTINTERFACE_USEBOOST
#include <boost/regex.hpp>
#include <boost/lexical_cast.hpp>
#endif
#define ERR(a) if (a < 0) return false;
namespace
{
asIScriptEngine* engine = 0;
asIScriptContext* ctx = 0;
Preprocessor::FileSource* file_source = 0;
Preprocessor::OutStream* error_stream = 0;
Preprocessor::LineNumberTranslator LNT;
int errorcode;
#ifdef JM_SCRIPTINTERFACE_USEBOOST
std::string regex_expression = "^([a-zA-Z._\\/]+)[[:space:]]+\\((\\d+)";
boost::regex expression(regex_expression);
bool ParseErrorMessage(std::string& msg, int& num)
{
std::string::const_iterator start, end;
start = msg.begin();
end = msg.end();
boost::match_results<std::string::const_iterator> results;
boost::match_flag_type flags = boost::match_default;
if (!boost::regex_search(start, end, results, expression, flags)) return false;
std::string num_str = std::string(results[2].first, results[2].second);
msg = std::string(results[0].second,end);
num = boost::lexical_cast<int>(num_str);
return true;
}
#endif
class ErrorTranslator: public asIOutputStream
{
public:
virtual void Write(const char* text)
{
//Parse error message...
if (!error_stream) return;
#ifdef JM_SCRIPTINTERFACE_USEBOOST
int lnumber;
std::string msgtext = std::string(text);
if (ParseErrorMessage(msgtext,lnumber))
{
(*error_stream) << LNT.ResolveOriginalFile(lnumber) << " ("
<< LNT.ResolveOriginalLine(lnumber) << msgtext;
} else {
(*error_stream) << std::string(text);
}
}
#else
(*error_stream) << std::string(text);
}
#endif
};
ErrorTranslator as_error_stream;
class ESFileSource: public Preprocessor::FileSource
{
private:
std::string str;
bool first_time;
public:
ESFileSource(const std::string& s)
: str(s), first_time(true) {}
virtual bool LoadFile(const std::string& filename, std::vector<char>& into)
{
if (first_time)
{
first_time = false;
char* d_end = &str[str.length()-1];
++d_end;
into.insert(into.end(),&str[0],d_end);
return true;
} else {
return file_source ? file_source->LoadFile(filename,into) : false;
}
}
};
class AngelStdCOut: public asIOutputStream
{
public:
virtual void Write(const char* text) { std::cout << text; }
};
};
bool ScriptInterface::Initialize(Preprocessor::FileSource* fs,Preprocessor::OutStream* os)
{
if (!fs || !os) return false;
engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);
RegisterStdString(engine);
ERR(engine->CreateContext(&ctx));
file_source = fs;
error_stream = os;
AngelStdCOut acout;
AutoBind::Bind(engine,&acout);
return true;
}
void ScriptInterface::Destroy()
{
if (ctx) ctx->Release();
if (engine) engine->Release();
ctx = 0;
engine = 0;
file_source = 0;
error_stream = 0;
}
void ScriptInterface::DiscardScript(const std::string& str)
{
engine->Discard(str.c_str());
}
static int FetchFunction(const std::string& script,
const std::string& func)
{
if (!engine) return -1;
int m_index = engine->GetModuleIndex(script.c_str());
if (m_index < 0)
{
if (!file_source) return -1;
if (!error_stream) return -1;
Preprocessor::VectorOutStream VOS;
bool error = false;
while (true)
{
if (Preprocessor::preprocess(script,*file_source,VOS,*error_stream,&LNT) > 0)
{
error = true; break;
}
errorcode = engine->AddScriptSection(
script.c_str(),
script.c_str(),
VOS.data(),
static_cast<int>(VOS.size()),0);
if (errorcode < 0) { error = true; break; }
errorcode = engine->Build(script.c_str(),&as_error_stream);
if (errorcode < 0) { error = true; break; }
break;
}
if (error) engine->Discard(script.c_str());
}
int r = engine->GetFunctionIDByName(script.c_str(),func.c_str());
if (r < 0)
{
//emit function-not-found error?
}
return r;
}
bool ScriptInterface::ExecuteString(const std::string& script)
{
ESFileSource EFS = ESFileSource(script);
Preprocessor::VectorOutStream VOS;
if (Preprocessor::preprocess("ExecuteString",EFS,VOS,*error_stream) > 0)
{
return false;
}
std::string preprocessed_script = std::string(VOS.data(),VOS.size());
int error = engine->ExecuteString(
0,
preprocessed_script.c_str(),
&as_error_stream,
&ctx,
asEXECSTRING_USE_MY_CONTEXT);
return (error >= 0);
}
bool ScriptInterface::ExecuteFunction(
const std::string& script,
const std::string& func)
{
int f_index = FetchFunction(script,func);
if (f_index < 0) return false;
ERR(ctx->Prepare(f_index));
ERR(ctx->Execute());
return true;
}
AutoBind.h
#ifndef JM_SERVER_AUTOBIND_H
#define JM_SERVER_AUTOBIND_H
#include <angelscript.h>
#define REG_NUM __LINE__
#define REG_CLASS REG_CAT(REGISTER,REG_NUM)
#define REG_INSTANCE REG_CAT(REGISTER_INSTANCE,REG_NUM)
//Normally, the preprocessor pastes tokens before expanding them. REG_CAT gets around
//that problem by delaying concatanation until after expansion. I don't know how it
//works; I took this trick from boost.
#define REG_CAT(a,b) REG_CAT_I(a,b)
#define REG_CAT_I(a,b) REG_CAT_II(a ## b)
#define REG_CAT_II(a) a
//Must be in a CPP file. Inserts a call to AutoBind::XXX at program
//startup. Hides identifiers in anonymous namespace - safe across compilation units.
#define REGISTER_FUNCTION(SIG,FUNC,CALL_CONV) namespace { class REG_CLASS { public: REG_CLASS() { AutoBind::BindFunction(SIG,FUNC,CALL_CONV); } }; REG_CLASS REG_INSTANCE ; };
#define REGISTER_TYPE(OBJ,TYPE,FLAGS) namespace { class REG_CLASS { public: REG_CLASS() { AutoBind::BindType(OBJ,sizeof(TYPE),FLAGS); } }; REG_CLASS REG_INSTANCE ; };
#define REGISTER_METHOD(OBJ,SIG,FUNC,CALL_CONV) namespace { class REG_CLASS { public: REG_CLASS() { AutoBind::BindMethod(OBJ,SIG,FUNC,CALL_CONV); } }; REG_CLASS REG_INSTANCE ; };
#define REGISTER_BEHAVIOR(BEHAVIOR,SIG,FUNC,CALL_CONV) namespace { class REG_CLASS { public: REG_CLASS() { AutoBind::BindBehavior(BEHAVIOR,SIG,FUNC,CALL_CONV); } }; REG_CLASS REG_INSTANCE ; };
#define REGISTER_TYPE_BEHAVIOR(OBJ,BEHAVIOR,SIG,FUNC,CALL_CONV) namespace { class REG_CLASS { public: REG_CLASS() { AutoBind::BindTypeBehavior(OBJ,BEHAVIOR,SIG,FUNC,CALL_CONV); } }; REG_CLASS REG_INSTANCE ; };
namespace AutoBind
{
//Processing and binds everything registered. Can only be called one - it destroyes
//the list. Optional stream receives signatures of everything bound.
void Bind(asIScriptEngine*, asIOutputStream *out = 0);
void BindFunction(const char*,asUPtr,asDWORD);
void BindType(const char*,int,asDWORD);
void BindBehavior(asDWORD,const char*,asUPtr,asDWORD);
void BindTypeBehavior(const char*,asDWORD,const char*,asUPtr,asDWORD);
void BindMethod(const char*, const char*,asUPtr,asDWORD);
};
#endif
AutoBind.cpp
#include "../scriptinterface.h"
#include "AutoBind.h"
#include <list>
#include <cassert>
namespace {
class Binder
{
public:
virtual void bind(asIScriptEngine*) = 0;
virtual ~Binder() {}
virtual void Emit(asIOutputStream *out) {}
};
class FunctionBinder: public Binder
{
public:
asUPtr fptr;
asDWORD call_conv;
const char* sig;
FunctionBinder(const char* str, asUPtr fp, asDWORD cc)
: sig(str), fptr(fp), call_conv(cc) {}
virtual void bind(asIScriptEngine* engine)
{
assert(engine->RegisterGlobalFunction(sig,fptr,call_conv) >= 0);
}
virtual void Emit(asIOutputStream *out)
{
out->Write(sig);
out->Write("\n");
}
};
class TypeBinder: public Binder
{
public:
asDWORD flags;
int size;
const char* ident;
TypeBinder(const char* i, int s, asDWORD t) : ident(i), size(s), flags(t) {}
virtual void bind(asIScriptEngine* engine)
{
assert(engine->RegisterObjectType(ident,size,flags) >= 0);
}
virtual void Emit(asIOutputStream *out)
{
out->Write("Type ");
out->Write(ident);
out->Write("\n");
}
};
class MethodBinder: public Binder
{
public:
const char* obj;
asUPtr fptr;
asDWORD call_conv;
const char* sig;
MethodBinder(const char* o, const char* s, asUPtr f, asDWORD c)
: sig(s), obj(o), fptr(f), call_conv(c) {}
virtual void bind(asIScriptEngine* engine)
{
assert(engine->RegisterObjectMethod(obj, sig, fptr, call_conv) >= 0);
}
virtual void Emit(asIOutputStream *out)
{
out->Write("Method on type ");
out->Write(obj);
out->Write(" : ");
out->Write(sig);
out->Write("\n");
}
};
class BehaviorBinder: public Binder
{
public:
asDWORD behave;
asUPtr fptr;
asDWORD call_conv;
const char* sig;
BehaviorBinder(asDWORD b, const char* s, asUPtr f, asDWORD c)
: sig(s), behave(b), fptr(f), call_conv(c) {}
virtual void bind(asIScriptEngine* engine)
{
assert(engine->RegisterGlobalBehaviour(behave, sig, fptr, call_conv) >= 0);
}
};
class TypeBehaviorBinder: public Binder
{
public:
const char* obj;
asDWORD behave;
asUPtr fptr;
asDWORD call_conv;
const char* sig;
TypeBehaviorBinder(const char* o, asDWORD b, const char* s, asUPtr f, asDWORD c)
: sig(s), obj(o), behave(b), fptr(f), call_conv(c) {}
virtual void bind(asIScriptEngine* engine)
{
assert(engine->RegisterObjectBehaviour(obj, behave, sig, fptr, call_conv) >= 0);
}
};
typedef std::list<Binder*> BinderList;
BinderList& GetBinder() //Static creation method
{
static BinderList binders;
return binders;
}
void PushBackBinder(Binder* b)
{
GetBinder().push_back(b);
}
void PushFrontBinder(Binder* b)
{
GetBinder().push_front(b);
}
};
void AutoBind::Bind(asIScriptEngine* engine, asIOutputStream *out)
{
while (!GetBinder().empty())
{
if (out) GetBinder().front()->Emit(out);
GetBinder().front()->bind(engine);
delete GetBinder().front();
GetBinder().pop_front();
}
}
void AutoBind::BindFunction(const char* s, asUPtr f, asDWORD c)
{
PushBackBinder(new FunctionBinder(s,f,c));
}
void AutoBind::BindType(const char* o, int s, asDWORD f)
{
PushFrontBinder(new TypeBinder(o,s,f));
}
void AutoBind::BindBehavior(asDWORD b, const char* s, asUPtr f, asDWORD c)
{
PushBackBinder(new BehaviorBinder(b,s,f,c));
}
void AutoBind::BindTypeBehavior(const char* o, asDWORD b, const char* s, asUPtr f, asDWORD c)
{
PushBackBinder(new TypeBehaviorBinder(o,b,s,f,c));
}
void AutoBind::BindMethod(const char* o, const char* s, asUPtr f, asDWORD c)
{
PushBackBinder(new MethodBinder(o,s,f,c));
}
Don't try and compile it; it won't unless you have Boost plus some of my own random headers.
Also; it doesn't rely on boost for much. Boost support can be compiled out, and all you lose is the translation of error messages from what AngelScript reports to the original file and line.