Browse Source

Add data expression transform functions (callbacks).

Michael Ragazzon 5 years ago
parent
commit
6ce69a8ebe

+ 2 - 2
Include/RmlUi/Core/DataController.h

@@ -61,7 +61,7 @@ public:
 protected:
 	DataController(Element* element);
 
-	void SetAddress(Address new_address) {
+	void SetAddress(DataAddress new_address) {
 		address = std::move(new_address);
 	}
 
@@ -70,7 +70,7 @@ protected:
 
 private:
 	ObserverPtr<Element> attached_element;
-	Address address;
+	DataAddress address;
 	Variant value;
 };
 

+ 17 - 6
Include/RmlUi/Core/DataModel.h

@@ -45,25 +45,27 @@ class Element;
 
 class RMLUICORE_API DataModel : NonCopyMoveable {
 public:
+	DataModel(const TransformFuncRegister* transform_register = nullptr) : transform_register(transform_register) {}
+
 	void AddView(UniquePtr<DataView> view) { views.Add(std::move(view)); }
 	void AddController(UniquePtr<DataController> controller) { controllers.Add(std::move(controller)); }
 
 	bool BindVariable(const String& name, Variable variable);
 
-	bool InsertAlias(Element* element, const String& alias_name, Address replace_with_address);
+	bool InsertAlias(Element* element, const String& alias_name, DataAddress replace_with_address);
 	bool EraseAliases(Element* element);
 
-	Address ResolveAddress(const String& address_str, Element* element) const;
+	DataAddress ResolveAddress(const String& address_str, Element* element) const;
 
-	Variable GetVariable(const Address& address) const;
+	Variable GetVariable(const DataAddress& address) const;
 
 	template<typename T>
-	bool GetValue(const Address& address, T& out_value) const {
+	bool GetValue(const DataAddress& address, T& out_value) const {
 		Variant variant;
 		Variable variable = GetVariable(address);
 		return variable && variable.Get(variant) && variant.GetInto<T>(out_value);
 	}
-	bool GetValue(const Address& address, Variant& out_value) const {
+	bool GetValue(const DataAddress& address, Variant& out_value) const {
 		Variable variable = GetVariable(address);
 		return variable && variable.Get(out_value);
 	}
@@ -71,6 +73,8 @@ public:
 	void DirtyVariable(const String& variable_name);
 	bool IsVariableDirty(const String& variable_name) const;
 
+	bool CallTransform(const String& name, Variant& inout_result, const VariantList& arguments) const;
+
 	void OnElementRemove(Element* element);
 	void DirtyController(Element* element);
 
@@ -83,8 +87,10 @@ private:
 	UnorderedMap<String, Variable> variables;
 	SmallUnorderedSet<String> dirty_variables;
 
-	using ScopedAliases = UnorderedMap<Element*, SmallUnorderedMap<String, Address>>;
+	using ScopedAliases = UnorderedMap<Element*, SmallUnorderedMap<String, DataAddress>>;
 	ScopedAliases aliases;
+
+	const TransformFuncRegister* transform_register;
 };
 
 
@@ -136,6 +142,11 @@ public:
 		return type_register->RegisterArray<Container>();
 	}
 
+
+	void RegisterTransformFunc(const String& name, DataTransformFunc transform_func) {
+		type_register->GetTransformFuncRegister()->Register(name, std::move(transform_func));
+	}
+
 	explicit operator bool() { return model && type_register; }
 
 private:

+ 46 - 1
Include/RmlUi/Core/DataVariable.h

@@ -52,6 +52,7 @@ enum class DataFunctionHandle : int {};
 
 using DataGetFunc = std::function<void(Variant&)>;
 using DataSetFunc = std::function<void(const Variant&)>;
+using DataTransformFunc = std::function<bool(Variant&, const VariantList&)>;
 
 template<typename T> using MemberGetFunc = void(T::*)(Variant&);
 template<typename T> using MemberSetFunc = void(T::*)(const Variant&);
@@ -63,7 +64,7 @@ struct AddressEntry {
 	String name;
 	int index;
 };
-using Address = std::vector<AddressEntry>;
+using DataAddress = std::vector<AddressEntry>;
 
 
 class RMLUICORE_API VariableDefinition {
@@ -311,8 +312,44 @@ private:
 
 
 
+
+
+class TransformFuncRegister {
+public:
+	void Register(const String& name, DataTransformFunc transform_func)
+	{
+		RMLUI_ASSERT(transform_func);
+		bool inserted = transform_functions.emplace(name, std::move(transform_func)).second;
+		if (!inserted)
+		{
+			Log::Message(Log::LT_ERROR, "Transform function '%s' already exists.", name.c_str());
+			RMLUI_ERROR;
+		}
+	}
+
+	bool Call(const String& name, Variant& inout_result, const VariantList& arguments) const
+	{
+		auto it = transform_functions.find(name);
+		if (it == transform_functions.end())
+			return false;
+
+		const DataTransformFunc& transform_func = it->second;
+		RMLUI_ASSERT(transform_func);
+
+		return transform_func(inout_result, arguments);
+	}
+
+private:
+	UnorderedMap<String, DataTransformFunc> transform_functions;
+};
+
+
+
 class DataTypeRegister : NonCopyMoveable {
 public:
+	DataTypeRegister();
+	~DataTypeRegister();
+
 	template<typename T>
 	StructHandle<T> RegisterStruct()
 	{
@@ -423,10 +460,18 @@ public:
 		return it->second.get();
 	}
 
+	TransformFuncRegister* GetTransformFuncRegister() {
+		return &transform_register;
+	}
+
+
 private:
 	UnorderedMap<DataFunctionHandle, UniquePtr<FuncDefinition>> functions;
 
 	UnorderedMap<FamilyId, UniquePtr<VariableDefinition>> type_register;
+
+	TransformFuncRegister transform_register;
+
 };
 
 

+ 1 - 1
Include/RmlUi/Core/DataView.h

@@ -143,7 +143,7 @@ public:
 	}
 
 private:
-	Address variable_address;
+	DataAddress variable_address;
 	String alias_name;
 	String rml_contents;
 	ElementAttributes attributes;

+ 2 - 0
Include/RmlUi/Core/StringUtilities.h

@@ -73,6 +73,8 @@ namespace StringUtilities
 
 	/// Converts upper-case characters in string to lower-case.
 	RMLUICORE_API String ToLower(const String& string);
+	/// Converts lower-case characters in string to upper-case.
+	RMLUICORE_API String ToUpper(const String& string);
 
 	/// Encode RML characters, eg. '<' to '&lt;'
 	RMLUICORE_API String EncodeRml(const String& string);

+ 1 - 0
Include/RmlUi/Core/Types.h

@@ -157,6 +157,7 @@ using SmallUnorderedSet = chobo::flat_set< T >;
 // Container types for common classes
 using ElementList = std::vector< Element* >;
 using OwnedElementList = std::vector< ElementPtr >;
+using VariantList = std::vector< Variant >;
 using ElementAnimationList = std::vector< ElementAnimation >;
 
 using PseudoClassList = SmallUnorderedSet< String >;

+ 1 - 1
Samples/basic/databinding/data/databinding.rml

@@ -170,7 +170,7 @@ form h2
 	<h1>{{delightful_invader.name}}</h1>
 	<img data-attr-sprite="delightful_invader.sprite" data-style-image-color="delightful_invader.sprite == 'icon-invader' ? 'black' : 'green'"/>
 	<p>
-		Indices: <span style="padding-left: 1em;" data-for="i : indices"> {{ i * 2 + (i > 10 ? ' wow!' : '') }}</span>
+		Indices: <span style="padding-left: 1em;" data-for="i : indices"> {{ i * 2 + (i > 10 ? ' wow!' | to_upper : '') }}</span>
 	</p>
 	<div data-for="invader : invaders">
 		<h1>{{invader.name}}</h1>

+ 4 - 7
Source/Core/Context.cpp

@@ -790,18 +790,15 @@ void Context::SetInstancer(ContextInstancer* _instancer)
 
 DataModelHandle Context::CreateDataModel(const String& name)
 {
-	auto result = data_models.emplace(name, std::make_unique<DataModel>());
+	if (!data_type_register)
+		data_type_register = std::make_unique<DataTypeRegister>();
+
+	auto result = data_models.emplace(name, std::make_unique<DataModel>(data_type_register->GetTransformFuncRegister()));
 	bool inserted = result.second;
 	if (inserted)
-	{
-		if (!data_type_register)
-			data_type_register = std::make_unique<DataTypeRegister>();
-
 		return DataModelHandle(result.first->second.get(), data_type_register.get());
-	}
 
 	RMLUI_ERRORMSG("Data model name already exists.")
-
 	return DataModelHandle();
 }
 

+ 1 - 1
Source/Core/DataController.cpp

@@ -57,7 +57,7 @@ bool DataController::UpdateVariable(DataModel& model)
 
 DataControllerValue::DataControllerValue(DataModel& model, Element* element, const String& in_value_name) : DataController(element)
 {
-    Address variable_address = model.ResolveAddress(in_value_name, element);
+    DataAddress variable_address = model.ResolveAddress(in_value_name, element);
 
     if (model.GetVariable(variable_address) && !variable_address.empty())
     {

+ 22 - 15
Source/Core/DataModel.cpp

@@ -33,22 +33,22 @@ namespace Rml {
 namespace Core {
 
 
-static Address ParseAddress(const String& address_str)
+static DataAddress ParseAddress(const String& address_str)
 {
 	StringList list;
 	StringUtilities::ExpandString(list, address_str, '.');
 
-	Address address;
+	DataAddress address;
 	address.reserve(list.size() * 2);
 
 	for (const auto& item : list)
 	{
 		if (item.empty())
-			return Address();
+			return DataAddress();
 
 		size_t i_open = item.find('[', 0);
 		if (i_open == 0)
-			return Address();
+			return DataAddress();
 
 		address.emplace_back(item.substr(0, i_open));
 
@@ -56,11 +56,11 @@ static Address ParseAddress(const String& address_str)
 		{
 			size_t i_close = item.find(']', i_open + 1);
 			if (i_close == String::npos)
-				return Address();
+				return DataAddress();
 
 			int index = FromString<int>(item.substr(i_open + 1, i_close - i_open), -1);
 			if (index < 0)
-				return Address();
+				return DataAddress();
 
 			address.emplace_back(index);
 
@@ -92,7 +92,7 @@ bool DataModel::BindVariable(const String& name, Variable variable)
 	return true;
 }
 
-bool DataModel::InsertAlias(Element* element, const String& alias_name, Address replace_with_address)
+bool DataModel::InsertAlias(Element* element, const String& alias_name, DataAddress replace_with_address)
 {
 	if (replace_with_address.empty() || replace_with_address.front().name.empty())
 	{
@@ -103,7 +103,7 @@ bool DataModel::InsertAlias(Element* element, const String& alias_name, Address
 	if (variables.count(alias_name) == 1)
 		Log::Message(Log::LT_WARNING, "Alias variable '%s' is shadowed by a global variable.", alias_name.c_str());
 
-	auto& map = aliases.emplace(element, SmallUnorderedMap<String, Address>()).first->second;
+	auto& map = aliases.emplace(element, SmallUnorderedMap<String, DataAddress>()).first->second;
 	
 	auto it = map.find(alias_name);
 	if (it != map.end())
@@ -119,9 +119,9 @@ bool DataModel::EraseAliases(Element* element)
 	return aliases.erase(element) == 1;
 }
 
-Address DataModel::ResolveAddress(const String& address_str, Element* element) const
+DataAddress DataModel::ResolveAddress(const String& address_str, Element* element) const
 {
-	Address address = ParseAddress(address_str);
+	DataAddress address = ParseAddress(address_str);
 
 	if (address.empty())
 		return address;
@@ -144,11 +144,11 @@ Address DataModel::ResolveAddress(const String& address_str, Element* element) c
 			auto it_alias_name = alias_names.find(first_name);
 			if (it_alias_name != alias_names.end())
 			{
-				const Address& replace_address = it_alias_name->second;
+				const DataAddress& replace_address = it_alias_name->second;
 				if (replace_address.empty() || replace_address.front().name.empty())
 				{
 					// Variable alias is invalid
-					return Address();
+					return DataAddress();
 				}
 
 				// Insert the full alias address, replacing the first element.
@@ -163,10 +163,10 @@ Address DataModel::ResolveAddress(const String& address_str, Element* element) c
 
 	Log::Message(Log::LT_WARNING, "Could not find variable name '%s' in data model.", address_str.c_str());
 
-	return Address();
+	return DataAddress();
 }
 
-Variable DataModel::GetVariable(const Address& address) const
+Variable DataModel::GetVariable(const DataAddress& address) const
 {
 	if (address.empty() || address.front().name.empty())
 		return Variable();
@@ -197,6 +197,13 @@ bool DataModel::IsVariableDirty(const String& variable_name) const {
 	return (dirty_variables.count(variable_name) == 1);
 }
 
+bool DataModel::CallTransform(const String& name, Variant& inout_result, const VariantList& arguments) const
+{
+	if (transform_register)
+		return transform_register->Call(name, inout_result, arguments);
+	return false;
+}
+
 void DataModel::OnElementRemove(Element* element)
 {
 	EraseAliases(element);
@@ -276,7 +283,7 @@ static struct TestDataVariables {
 
 			for (auto& str_address : test_addresses)
 			{
-				Address address = ParseAddress(str_address);
+				DataAddress address = ParseAddress(str_address);
 
 				String result;
 				if(model.GetValue<String>(address, result))

+ 98 - 76
Source/Core/DataParser.cpp

@@ -103,7 +103,7 @@ namespace Parse {
 
 class DataParser {
 public:
-	DataParser(String expression, DataVariableInterface variable_interface = {}) : expression(std::move(expression)), variable_interface(variable_interface) {}
+	DataParser(String expression, DataVariableInterface variable_interface) : expression(std::move(expression)), variable_interface(variable_interface) {}
 
 	char Look() {
 		if (reached_end)
@@ -135,35 +135,36 @@ public:
 			c = Next();
 	}
 
-	void Error(String message)
+	void Error(const String message)
 	{
 		parse_error = true;
-		message = CreateString(message.size() + expression.size() + 50, "Error in expression '%s' at %d. %s", expression.c_str(), index, message.c_str());
-		Log::Message(Log::LT_WARNING, message.c_str());
+		Log::Message(Log::LT_WARNING, "Error in data expression at %d. %s", index, message.c_str());
+		Log::Message(Log::LT_WARNING, "  \"%s\"", expression.c_str());
 		
-		const size_t cursor_offset = size_t(index) + sizeof("Error in expression ");
+		const size_t cursor_offset = size_t(index) + 3;
 		const String cursor_string = String(cursor_offset, ' ') + '^';
 		Log::Message(Log::LT_WARNING, cursor_string.c_str());
 	}
-	void Expected(char expected) {
-		char c = Look();
+	void Expected(String expected_symbols) {
+		const char c = Look();
 		if (c == '\0')
-			Error(CreateString(50, "Expected '%c' but found end of string.", expected));
+			Error(CreateString(expected_symbols.size() + 50, "Expected %s but found end of string.", expected_symbols.c_str()));
 		else
-			Error(CreateString(50, "Expected '%c' but found '%c'.", expected, c));
+			Error(CreateString(expected_symbols.size() + 50, "Expected %s but found character '%c'.", expected_symbols.c_str(), c));
 	}
-	void Expected(String expected_symbols) {
-		Error(CreateString(expected_symbols.size() + 50, "Expected %s but found character '%c'.", expected_symbols.c_str(), Look()));
+	void Expected(char expected) {
+		Expected(String(1, '\'') + expected + '\'');
 	}
 
 	bool Parse() 
 	{
-		Log::Message(Log::LT_DEBUG, "Parsing expression: %s", expression.c_str());
 		program.clear();
 		variable_addresses.clear();
 		index = 0;
 		reached_end = false;
 		parse_error = false;
+		if (expression.empty())
+			reached_end = true;
 
 		SkipWhitespace();
 		Parse::Expression(*this);
@@ -172,8 +173,10 @@ public:
 			parse_error = true;
 			Error(CreateString(50, "Unexpected character '%c' encountered.", Look()));
 		}
-		if (!parse_error)
-			Log::Message(Log::LT_DEBUG, "Finished parsing expression! Instructions: %d   Stack depth: %d", program.size(), program_stack_size);
+		if (!parse_error && program_stack_size != 0) {
+			parse_error = true;
+			Error(CreateString(120, "Internal parser error, inconsistent stack operations. Stack size is %d at parse end.", program_stack_size));
+		}
 
 		return !parse_error;
 	}
@@ -215,9 +218,9 @@ public:
 		program.push_back(InstructionData{ Instruction::Arguments, Variant(int(num_arguments)) });
 	}
 	void Variable(const String& name) {
-		Address address = variable_interface.ParseAddress(name);
+		DataAddress address = variable_interface.ParseAddress(name);
 		if (address.empty()) {
-			Error(CreateString(name.size() + 50, "Invalid variable name '%s'.", name.c_str()));
+			Error(CreateString(name.size() + 50, "Could not find data variable with name '%s'.", name.c_str()));
 			return;
 		}
 		int index = int(variable_addresses.size());
@@ -652,6 +655,16 @@ namespace Parse {
 
 
 
+static String DumpProgram(const Program& program)
+{
+	String str;
+	for (size_t i = 0; i < program.size(); i++)
+	{
+		String instruction_str = program[i].data.Get<String>();
+		str += CreateString(instruction_str.size(), "  %4d  '%c'  %s\n", i, char(program[i].instruction), instruction_str.c_str());
+	}
+	return str;
+}
 
 
 class DataInterpreter {
@@ -668,8 +681,6 @@ public:
 
 	bool Run()
 	{
-		Log::Message(Log::LT_DEBUG, "Executing program");
-		DumpProgram();
 		bool success = true;
 		for (size_t i = 0; i < program.size(); i++)
 		{
@@ -680,15 +691,15 @@ public:
 			}
 		}
 
-		if (success)
-			Log::Message(Log::LT_DEBUG, "Successfully finished execution of program with %d instructions.", program.size());
-		else
-			Log::Message(Log::LT_WARNING, "Failed executing program with %d instructions.", program.size());
+		if(success && !stack.empty())
+			Log::Message(Log::LT_WARNING, "Possible data interpreter stack corruption. Stack size is %d at end of execution (should be zero).", stack.size());
 
-		Log::Message(Log::LT_DEBUG, "R: %s", R.Get<String>().c_str());
-		Log::Message(Log::LT_DEBUG, "L: %s", L.Get<String>().c_str());
-		Log::Message(Log::LT_DEBUG, "C: %s", C.Get<String>().c_str());
-		Log::Message(Log::LT_DEBUG, "Stack #: %d", stack.size());
+		if(!success)
+		{
+			String program_str = DumpProgram(program);
+			Log::Message(Log::LT_WARNING, "Failed to execute program with %d instructions:", program.size());
+			Log::Message(Log::LT_WARNING, program_str.c_str());
+		}
 
 		return success;
 	}
@@ -697,15 +708,6 @@ public:
 		return R;
 	}
 
-	void DumpProgram()
-	{
-		int i = 0;
-		for (auto& instruction : program)
-		{
-			Log::Message(Log::LT_DEBUG, "  %4d  '%c'  %s", i, char(instruction.instruction), instruction.data.Get<String>().c_str());
-			i++;
-		}
-	}
 
 private:
 	Variant R, L, C;
@@ -741,7 +743,7 @@ private:
 			case Register::L:  L = stack.top(); stack.pop(); break;
 			case Register::C:  C = stack.top(); stack.pop(); break;
 			default:
-				return Error(CreateString(50, "Invalid register %d", int(reg)));
+				return Error(CreateString(50, "Invalid register %d.", int(reg)));
 			}
 		}
 		break;
@@ -767,16 +769,16 @@ private:
 				R = Variant(L.Get<double>() + R.Get<double>());
 		}
 		break;
-		case Instruction::Subtract: R = Variant(L.Get<double>() - R.Get<double>()); break;
-		case Instruction::Multiply: R = Variant(L.Get<double>() * R.Get<double>()); break;
-		case Instruction::Divide:   R = Variant(L.Get<double>() / R.Get<double>()); break;
-		case Instruction::Not:      R = Variant(!R.Get<bool>()); break;
-		case Instruction::And:      R = Variant(L.Get<bool>() && R.Get<bool>());  break;
-		case Instruction::Or:       R = Variant(L.Get<bool>() || R.Get<bool>());  break;
-		case Instruction::Less:       R = Variant(L.Get<double>() < R.Get<double>());  break;
-		case Instruction::LessEq:     R = Variant(L.Get<double>() <= R.Get<double>()); break;
-		case Instruction::Greater:    R = Variant(L.Get<double>() > R.Get<double>());  break;
-		case Instruction::GreaterEq:  R = Variant(L.Get<double>() >= R.Get<double>()); break;
+		case Instruction::Subtract:  R = Variant(L.Get<double>() - R.Get<double>());  break;
+		case Instruction::Multiply:  R = Variant(L.Get<double>() * R.Get<double>());  break;
+		case Instruction::Divide:    R = Variant(L.Get<double>() / R.Get<double>());  break;
+		case Instruction::Not:       R = Variant(!R.Get<bool>());                     break;
+		case Instruction::And:       R = Variant(L.Get<bool>() && R.Get<bool>());     break;
+		case Instruction::Or:        R = Variant(L.Get<bool>() || R.Get<bool>());     break;
+		case Instruction::Less:      R = Variant(L.Get<double>() < R.Get<double>());  break;
+		case Instruction::LessEq:    R = Variant(L.Get<double>() <= R.Get<double>()); break;
+		case Instruction::Greater:   R = Variant(L.Get<double>() > R.Get<double>());  break;
+		case Instruction::GreaterEq: R = Variant(L.Get<double>() >= R.Get<double>()); break;
 		case Instruction::Equal:
 		{
 			if (AnyString(L, R))
@@ -802,7 +804,7 @@ private:
 		case Instruction::Arguments:
 		{
 			if (!arguments.empty())
-				return Error("Invalid program: Argument stack is not empty.");
+				return Error("Argument stack is not empty.");
 
 			int num_arguments = data.Get<int>(-1);
 			if (num_arguments < 0)
@@ -821,16 +823,19 @@ private:
 		case Instruction::Function:
 		{
 			const String function_name = data.Get<String>();
-
-			String arguments_str;
-			for (size_t i = 0; i < arguments.size(); i++)
+			
+			if (!variable_interface.CallTransform(function_name, R, arguments))
 			{
-				arguments_str += arguments[i].Get<String>();
-				if (i < arguments.size() - 1)
-					arguments_str += ", ";
+				String arguments_str;
+				for (size_t i = 0; i < arguments.size(); i++)
+				{
+					arguments_str += arguments[i].Get<String>();
+					if (i < arguments.size() - 1)
+						arguments_str += ", ";
+				}
+				Error(CreateString(50 + function_name.size() + arguments_str.size(), "Failed to execute data function: %s(%s)", function_name.c_str(), arguments_str.c_str()));
 			}
-			// TODO: execute function
-			Log::Message(Log::LT_DEBUG, "Executing '%s' with %d argument(s): %s(%s)", function_name.c_str(), arguments.size(), function_name.c_str(), arguments_str.c_str());
+
 			arguments.clear();
 		}
 		break;
@@ -846,29 +851,36 @@ private:
 
 
 struct TestParser {
-	TestParser() {
-
-		//DataParser("'hello' + ' ' + 'world'").Parse();
-		//DataParser("5+(1+2)").Parse();
-		//DataParser("5.2 + 19 + 'test'").Parse();
-		//DataParser("(color_name) + (': rgba(' + color_value + ')')").Parse();
-		//DataParser("!!10 - 1 ? 'hello' : 'world'").Parse();
-		//int test = 1 + (true ? 0-5 : 10 + 5);
-		//DataParser("1 + (true ? 0-5 : 10 + 5)").Parse();
-		String result;
-		result = TestExpression("'hello world' | uppercase(5 + 12 == 17 ? 'yes' : 'no', 9*2)");
-		result = TestExpression(R"('hello wor\'ld')");
-
-		String alt = "hello wor\ld";
+	TestParser() : model(type_register.GetTransformFuncRegister())
+	{
+		DataModelHandle handle(&model, &type_register);
+		handle.Bind("color_name", &color_name);
+		handle.BindFunc("color_value", [this](Rml::Core::Variant& variant) {
+			variant = ToString(color_value);
+		});
 
+		String result;
+		result = TestExpression("!!10 - 1 ? 'hello' : 'world' | to_upper");
+		result = TestExpression("(color_name) + (': rgba(' + color_value + ')')");
+		result = TestExpression("'hello world' | to_upper(5 + 12 == 17 ? 'yes' : 'no', 9*2)");
+		result = TestExpression("true == false");
+		result = TestExpression("true != false");
+		result = TestExpression("true");
+		result = TestExpression(R"(true || false ? (true && true ? 'Absolutely!' : 'well..') : 'no')");
+		result = TestExpression("2 * 2");
+		result = TestExpression("50000 / 1500");
+		result = TestExpression("5*1+2");
+		result = TestExpression("5*(1+2)");
+		result = TestExpression("5.2 + 19 + 'px'");
 	}
 
 	String TestExpression(String expression)
 	{
-		DataParser parser(expression);
+		DataVariableInterface interface(&model, nullptr);
+
+		DataParser parser(expression, interface);
 		if (parser.Parse())
 		{
-			DataVariableInterface interface;
 			Program program = parser.ReleaseProgram();
 			AddressList addresses = parser.ReleaseAddresses();
 
@@ -878,6 +890,12 @@ struct TestParser {
 		}
 		return "<invalid expression>";
 	};
+
+	DataTypeRegister type_register;
+	DataModel model;
+
+	String color_name = "color";
+	Colourb color_value = Colourb(180, 100, 255);
 };
 
 
@@ -897,7 +915,6 @@ bool DataExpression::Parse(const DataVariableInterface& variable_interface)
 	// TODO:
 	//  3. Create a plug-in wrapper for use by scripting languages to replace this parser. Design wrapper as for events.
 	//  5. Add tests
-	//  6. Function callback
 
 	DataParser parser(expression, variable_interface);
 	if (!parser.Parse())
@@ -924,7 +941,7 @@ StringList DataExpression::GetVariableNameList() const
 {
 	StringList list;
 	list.reserve(addresses.size());
-	for (const Address& address : addresses)
+	for (const DataAddress& address : addresses)
 	{
 		if (!address.empty())
 			list.push_back(address[0].name);
@@ -935,15 +952,20 @@ StringList DataExpression::GetVariableNameList() const
 DataVariableInterface::DataVariableInterface(DataModel* data_model, Element* element) : data_model(data_model), element(element)
 {}
 
-Address DataVariableInterface::ParseAddress(const String& address_str) const {
-	return data_model ? data_model->ResolveAddress(address_str, element) : Address();
+DataAddress DataVariableInterface::ParseAddress(const String& address_str) const {
+	return data_model ? data_model->ResolveAddress(address_str, element) : DataAddress();
 }
-Variant DataVariableInterface::GetValue(const Address& address) const {
+Variant DataVariableInterface::GetValue(const DataAddress& address) const {
 	Variant result;
 	if (data_model)
 		data_model->GetValue(address, result);
 	return result;
 }
 
+bool DataVariableInterface::CallTransform(const String& name, Variant& inout_variant, const VariantList& arguments)
+{
+	return data_model ? data_model->CallTransform(name, inout_variant, arguments) : false;
+}
+
 }
 }

+ 4 - 3
Source/Core/DataParser.h

@@ -40,15 +40,16 @@ class Element;
 class DataModel;
 struct InstructionData;
 using Program = std::vector<InstructionData>;
-using AddressList = std::vector<Address>;
+using AddressList = std::vector<DataAddress>;
 
 class DataVariableInterface {
 public:
     DataVariableInterface() = default;
     DataVariableInterface(DataModel* data_model, Element* element);
 
-    Address ParseAddress(const String& address_str) const;
-    Variant GetValue(const Address& address) const;
+    DataAddress ParseAddress(const String& address_str) const;
+    Variant GetValue(const DataAddress& address) const;
+    bool CallTransform(const String& name, Variant& inout_result, const VariantList& arguments);
 
 private:
     DataModel* data_model = nullptr;

+ 26 - 0
Source/Core/DataVariable.cpp

@@ -49,5 +49,31 @@ Variable VariableDefinition::GetChild(void* ptr, const AddressEntry& address) {
     return Variable();
 }
 
+
+
+DataTypeRegister::DataTypeRegister()
+{
+    // Add default transform functions.
+
+	transform_register.Register("to_lower", [](Variant& variant, const VariantList& arguments) -> bool {
+		String value;
+		if (!variant.GetInto(value))
+			return false;
+		variant = StringUtilities::ToLower(value);
+		return true;
+	});
+
+	transform_register.Register("to_upper", [](Variant& variant, const VariantList& arguments) -> bool {
+		String value;
+		if (!variant.GetInto(value))
+			return false;
+		variant = StringUtilities::ToUpper(value);
+		return true;
+	});
+}
+
+DataTypeRegister::~DataTypeRegister()
+{}
+
 }
 }

+ 1 - 1
Source/Core/DataView.cpp

@@ -359,7 +359,7 @@ bool DataViewFor::Update(DataModel& model)
 		{
 			ElementPtr new_element_ptr = Factory::InstanceElement(nullptr, element->GetTagName(), element->GetTagName(), attributes);
 
-			Address replacement_address;
+			DataAddress replacement_address;
 			replacement_address.reserve(variable_address.size() + 1);
 			replacement_address = variable_address;
 			replacement_address.push_back(AddressEntry(i));

+ 12 - 0
Source/Core/StringUtilities.cpp

@@ -94,6 +94,18 @@ String StringUtilities::ToLower(const String& string) {
 	return str_lower;
 }
 
+String StringUtilities::ToUpper(const String& string)
+{
+	String str_upper = string;
+	std::transform(str_upper.begin(), str_upper.end(), str_upper.begin(), [](char c) {
+		if (c >= 'a' && c <= 'z')
+			c -= char('a' - 'A');
+		return c;
+		}
+	);
+	return str_upper;
+}
+
 RMLUICORE_API String StringUtilities::EncodeRml(const String& string)
 {
 	String result;