C++ implementation

1 The barebones

- 1.1 Error reporting

- 1.2 Garbage collector

- 1.3 Functions

2 Advanced

- 2.1 Variable protection

- 2.2 Userdata

- - 2.2.1 Creating userdata

- - 2.2.2 Userdata setters and getters

- - 2.2.3 Inheriting

- 2.3 Custom DLLs


1 The barebones

// Create the BromScript Instance 
Instance bsi; 

// Load libaries like Math, String, etc 
bsi.LoadDefaultLibaries(); 
// In case you want to disable specific libaries, use bsi.SetVar("Math", null) AFTER you ran bsi.LoadDefaultLibaries() 

// Run a simple string 
bsi.DoString("hello world", "print(\"Hello there!\")"); 

// Run a freaking file! Holy cow! 
bsi.DoFile("somefile.bs"); 

1.1 Error reporting

// We could also add an error reporting function to this, might be usefull! 
// The following line is defined in BromScript.h 
#define BS_ERRORFUNCTION(fn) void fn(Instance* bsi, CallStack* stack, int stacksize, const char* msg) 

BS_ERRORFUNCTION(errfunc){ 
	printf("BromScript error!\n"); 

	for (int i = 0; i < stacksize; i++) 
		printf("SID: %i, File: %s, Name: %s\n", i, stack[i].Filename, stack[i].Name); 

	printf("Message: %s\n", msg); 
} 
// And now lets set it in bsi 
bsi.SetErrorCallback(errfunc); 

// error("bla") output: 
// BromScript error! 
// SID: 1, File: somefile.bs, Name: somefunction 
// Message: bla 

1.2 Garbage collector

/*
	The garbage collector manages all variables inside of BromScript.
	The moment a variable enters BromScript's side, it'll be registered and it will be deleted when it feels so.
	
	That means that even if you create the variable yourself, you're not supposed to delete it.
	
	Every variable has a reference counter, when it drops to 0, it gets collected and removed.
	However, outside of the BromScript kingdom, we have no control over where the variable is stored.
	Therefore, if you want to prevent a variable from being collected, then increese and decreese the reference counter
*/

var->IncreaseRefCount();
var->DecreaseRefCount();

1.3 Functions

// The following line is defined in BromScript.h 
#define BS_FUNCTION(fn) Variable* fn(Instance* bsi, ArgumentData* args) 

// Argumentdata class, here you can retrieve all the arguments in a function call 
// One thing that is important to remember is: When you're handeling tables, or userdata types.
// args->ThisObject is the table/userdata 
// so when writing userdata wrappers, you can retrieve your userdata with args->GetThisObjectData()
// be sure to typecheck it with args->CheckThisObject! 
// when making wrappers, always typecheck!. 

BS_FUNCTION(SomeCppFunction){ 
	// lets check if its a number the user is inputting, by using the following black magic: 
	if (!args->CheckType(0, Variable::VariableType::Number, true)) return null; 
	 
	// You can return a new variable here, you can also return null. Whatever suits you. 
	return Converter::ToVariable(args->GetNumber(0) * 10); 
} 

// Aaaaand register it! 
bsi.RegisterFunction("somename", SomeCppFunction); 
// in BS: print(somename(5)) should output 50 

2.1 Variable protection

// Lets use the Math libary as example, the math libary is set, and you dont want people to override it with their own stuff.
// Simply doing this would be enough: 
bsi.GetVar("Math")->ReadOnly = true; 
// Do note however, that this is an internal check in the table class. So if you would do this in c++ now 
bsi.SetVar("Math", magic); // this would not work anymore, and will throw a BS error, which might mess up the callstack a bit. 
// However, if you do this 
bsi.RemoveVar("Math"); 
bsi.SetVar("Math", magic); 
// this does work, because you first forcefully remove it out of the table. dont worry,
// this wont generate memory leaks on our side 
// note: SetVar is a reference to this->Globals->Set, for a function the reference would be this->Locals->Set 

// this is usefull for when people are overriding things they should not override

2.2 Userdata

// The following lines are defined in BromScript.h 
#define BS_FUNCTION(fn) Variable* fn(Instance* bsi, ArgumentData* args) 
#define BS_FUNCTION_CTOR(fn) void* fn(Instance* bsi, ArgumentData* args) 
#define BS_FUNCTION_DTOR(fn) void fn(Instance* bsi, void* data) 

// The Userdata type number, this is the ID of the class 
#define VECTORDATATYPE 1337 
// Use numbers higher than 100 here, numbers below the 100 are reserved for functionality to be added to BS. 

// Our pwitty class 
class CVector{ 
public : 
	double x; 
	double y; 
	double z; 
}; 

// Our wrappers 
// Constructor of the class, return a pointer 
BS_FUNCTION_CTOR(CVectorCtor){ 
	CVector* ct = new CVector(); 

	ct->x = args->GetNumber(0); 
	ct->y = args->GetNumber(1); 
	ct->z = args->GetNumber(2); 

	// returning null here is fine too, BS will detect it and it will revert the variable to a null type 
	return ct; 
} 

// deconstructor, data is a pointer, delete this here, and do other stuff if you want to. 
BS_FUNCTION_DTOR(CVectorDtor){ 
	// warning, if you dont delete your data here, then you'll have to clean it up yourself, not deleting it
	// would be logical for an entity system to prevent crashes
	// like checking in the entitiy functions itself if a flag has been set 
	delete (CVector*)data; 
} 

// Wrapper functions 
BS_FUNCTION(CVectorSetFunc){ 
	CVector* ct = (CVector*)args->GetUserdata(0, VECTORDATATYPE); 
	ct->x = args->GetNumber(1); 
	ct->y = args->GetNumber(2); 
	ct->z = args->GetNumber(3); 

	return null; 
} 

BS_FUNCTION(CVectorGetXFunc){ 
	CVector* ct = (CVector*)args->GetUserdata(0, VECTORDATATYPE); 
	return Converter::ToVariable(ct->x); // again, returning null here is fine. 
} 

BS_FUNCTION(CVectorGetYFunc){ 
	CVector* ct = (CVector*)args->GetUserdata(0, VECTORDATATYPE); 
	return Converter::ToVariable(ct->y); 
} 

BS_FUNCTION(CVectorGetZFunc){ 
	CVector* ct = (CVector*)args->GetUserdata(0, VECTORDATATYPE); 
	return Converter::ToVariable(ct->z); 
} 

BS_FUNCTION(CVector__AddOperator) {
	// You can also register Operators, these work the same way as normal BSFunctions 
	// However, they always get called with 2 arguments, a + b, a is index 0, b is index 1. 
	// As example, a is an CVector class, and b is a number. you can do a + b
	// this will make the second argument a number, and the first one a CVector 
	// However, if you do b + a, the same function also gets called. at that moment
	// the 0 index argument will be the number and not the CVector 
	// So you'll need to typecheck to see what a is, and what b is. 

	if (!args->CheckType(0, VECTORDATATYPE)) return null;
	if (!args->CheckType(1, VECTORDATATYPE)) return null;

	CVector* leftvalue = (CVector*)args->GetUserdata(0, VECTORDATATYPE);
	CVector* rightvalue = (CVector*)args->GetUserdata(1, VECTORDATATYPE);
	
	CVector* newvec = new CVector();
	newvec->x = leftvalue->x + rightvalue->x;
	newvec->y = leftvalue->y + rightvalue->y;
	newvec->z = leftvalue->z + rightvalue->z;
	
	return args->BromScript->CreateUserdata(VECTORDATATYPE, newvec, true);
}

// Register everything in the bsi, the CTor and DTor can be null, it'll throw an error inside BS when the user
// tries to create a type, and it will just not call the dtor incase that's null(watch out with leaks here!) 
Userdata* vd = bsi.RegisterUserdata("CVector", VECTORDATATYPE, sizeof(CVector), CVectorCtor, CVectorDtor); 

// add indexes for x, y and x so we can do "vector.x = 15"
// Don't use string as membertype unless you're also supplying a getter and setter, using them witouth will cause memory leaks
// BS has no idea if it should delete old ones, or keep them in memory
// should it allocate and put a pointer back? or is it an fixed sized array? We dont know!
vd->RegisterMember("x", offsetof(CVector, x), MemberType::Double); 
vd->RegisterMember("y", offsetof(CVector, y), MemberType::Double); 
vd->RegisterMember("z", offsetof(CVector, z), MemberType::Double); 

// Our wrapper funcs 
vd->RegisterFunction("Set", CVectorSetFunc); 
vd->RegisterFunction("GetX", CVectorGetXFunc); 
vd->RegisterFunction("GetY", CVectorGetYFunc); 
vd->RegisterFunction("GetZ", CVectorGetZFunc); 

// Our add operator
vd->RegisterOperator(Operators::ArithmeticAdd, CVector__AddOperator);

// And now, we can use the following code to create a new Vector in BromScript 
// local vec = new Vector() 

2.2.1 Creating userdata

// Somewhere in a magical place 
#define CLASSDATATYPE 1337 

// The true at the end here is for the deconstructor 
// This, my kind sir, is quite important in case you want the Dtor to be called when it's removed by the garbage collector 
// when you use new *class* in BS, this will get set to true. 
Variable* var = bsi->CreateUserData(CLASSDATATYPE, new SomeClass(), true); 

return var; 

2.2.2 Userdata setters and getters

// Lets add an CString called MyString to our Vector class. Why a string? Because fuck you thats why. 

// Once again, these are magicly created for you. 
#define BS_USERDATA_SET(fn) void fn(Instance* bsi, void* ptr, Variable* var) 
#define BS_USERDATA_GET(fn) Variable* fn(Instance* bsi, void* ptr) 

BS_USERDATA_SET(CVectorMyStringSet){ 
	// Variable has functions like GetString, it has a type check inside to make sure it wont crash
	// when you use ->GetString on them. However, It wont throw an error for you. 
	// So we'll have to do that ourselfs by calling this function, which will do that work for you
	if (!args->CheckType(0, VariableType::String)) return null;
	 
	// Be sure to copy the value here, because the moment the variable gets cleaned by the GC, the var->Value will get deleted 
	// In case you want to keep some data, like tables, use var->IncreaseRefCount() on it
	// to prevent it from getting garbage collected. (REMEMBER TO DO var->DecreaseRefCount()!) 
	*(Scratch::CString*)ptr = Scratch::CString(var->GetString()); 
} 

BS_USERDATA_GET(CVectorMyStringGet){
	// cast it to our string class
	Scratch::CString str = (Scratch::CString*)ptr; 
	 
	// You can return null here too. If that's what you want, your wish is my command my lord. 
	return Converter::ToVariable(str); 
} 

// And then we add this to our code 
ud->RegisterMember("MyString", offsetof(CVector, MyString), MemberType::String, CVectorMyStringSet, CVectorMyStringGet); 
// And now, we can do this: vecobj.MyString = "woo" 

2.2.3 Inheriting

// What's objective programming without over engineering class structures!
// If you have something like Entity_Base and Entity_NPC, then you can simply create a inheritance tree like so:

BromScript::Userdata* udParent = bsi.RegisterUserdata("TestParent", 1000, 0, TestParentCTOR, nullptr);
udParent->RegisterFunction("a", a);
udParent->RegisterFunction("c", a);

BromScript::Userdata* udChild = bsi.RegisterUserdata("TestChild", 1001, 0, TestChildCTOR, nullptr);
udChild->InheritFrom = udParent; // where the magic happens
// or: udChild->InheritFrom = bsi.GetRegisteredUserdata(1000); // 1000 being the typeID, which refers to TestParent

udChild->RegisterFunction("b", b);
udChild->RegisterFunction("c", c); // override c, because we don't want to call the base function a, but our own!

// now we have a object called TestChild with a, b and c methods
// and a TestParent with a and c in it as methods

2.3 Custom DLLs

// Dlls can be created simply too, and in BS you can then use the function includeDll to load it. 
// The following line is defined in BromScript.h 
#define BS_MODULE(fn) void fn(Instance* bsi) 

#include <BromScript.h> 
using namespace BromScript; 

#define DLL_EXPORT extern "C" __declspec(dllexport) 
DLL_EXPORT BS_MODULE(BromScriptStart){ 
	Variable* msg = Converter::ToVariable("The dll got loaded, WARGGGGGG!"); 
	 
	// vars get registered in the garbage collector when they enter the BromScript empire
	// so we don't need to do that ourselfs
	 
	List<Variable*> args; 
	args.Add(msg); 
	bsi->GetVar("print")->GetFunction()->Run(&args); 
} 

DLL_EXPORT BS_MODULE(BromScriptShutdown){ 
	// bsi gets unloaded in the following order: 
	// 1> calls shutdown on all dlls, because this is the first step, you can still call functions and do other stuff 
	// 2> removes scopes (every time you use DoString/DoFile/DoCode you create a local scope) 
	// 3> removes globals 
	// 4> calls the GC to clean everything up 
	// 5> deletes and removes all remaining pointers 

	Variable* msg = Converter::ToVariable("The bsi instance is being destroyed, noooooooo!"); 
	bsi->GC.RegisterVariable(msg); 

	List<Variable*> args; 
	args.Add(msg); 
	bsi->GetVar("print")->GetFunction()->Run(&args); 
}