ValueHandler instead of StringHandler interface; Guile now allows filtering and record modifications v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Sun, 27 Jan 2019 17:57:03 +0100
branchv_0
changeset 5 17bb45570099
parent 4 9b4fe4bc5f0f
child 6 4062b8436838
ValueHandler instead of StringHandler interface; Guile now allows filtering and record modifications
src/GuileHandler.h
--- a/src/GuileHandler.h	Tue Jan 22 16:12:32 2019 +0100
+++ b/src/GuileHandler.h	Sun Jan 27 17:57:03 2019 +0100
@@ -25,12 +25,13 @@
 #include <locale>
 #include <codecvt>
 #include <regex>
+#include <assert.h>
 
 #include <libguile.h>
 
 #include <relpipe/reader/typedefs.h>
 #include <relpipe/reader/TypeId.h>
-#include <relpipe/reader/handlers/RelationalReaderStringHandler.h>
+#include <relpipe/reader/handlers/RelationalReaderValueHandler.h>
 #include <relpipe/reader/handlers/AttributeMetadata.h>
 
 #include <relpipe/writer/Factory.h>
@@ -46,59 +47,100 @@
 using namespace relpipe::reader;
 using namespace relpipe::reader::handlers;
 
-class GuileHandler : public RelationalReaderStringHadler {
+class GuileHandler : public RelationalReaderValueHadler {
 private:
-	std::wstring_convert<codecvt_utf8<wchar_t>> convertor; // TODO: support also other encodings.
+	std::wstring_convert<codecvt_utf8<wchar_t>> convertor; // TODO: support also other encodings or use always UTF-8 between C++ and Guile
 
 	shared_ptr<writer::RelationalWriter> relationalWriter;
 
 	wregex relationNameRegEx;
 
-	vector<AttributeMetadata> currentMetadata;
+	vector<AttributeMetadata> currentReaderMetadata;
+	vector<writer::AttributeMetadata> currentWriterMetadata;
 	vector<string_t> currentRecord;
 	integer_t currentAttributeIndex = 0;
 	boolean_t includeCurrentRecord = false;
 	boolean_t filterCurrentRelation = false;
-	string_t guileCode;
+	string_t guileCodeWhereCondition;
 
-	SCM toGuileSymbol(const string_t& name) {
-		return scm_string_to_symbol(scm_from_locale_string(convertor.to_bytes(L"$" + name).c_str()));
+	/**
+	 * @param attributeName name from relational pipe
+	 * @return variable name in Guile
+	 */
+	string_t a2v(const string_t& attributeName) {
+		// TODO: escape spaces and special characters
+		return L"$" + attributeName;
 	}
 
-	SCM toGuileString(const string_t& value) {
-		return scm_from_locale_string(convertor.to_bytes(value).c_str());
+	SCM toGuileSymbol(const string_t& name) {
+		return scm_string_to_symbol(scm_from_locale_string(convertor.to_bytes(name).c_str()));
 	}
 
-	SCM toGuileInteger(const string_t& value) {
-		return scm_from_uint64(stoul(value));
+	SCM evalGuileCode(const string_t& value) {
+		return scm_eval_string(toGuileValue(&value, typeid (string_t), TypeId::STRING));
 	}
 
-	SCM toGuileBoolean(const string_t& value) {
-		return value == L"true" ? SCM_BOOL_T : SCM_BOOL_F;
-	}
-
-	void defineGuileVariable(const string_t& name, TypeId type, const string_t& value) {
-		// TODO: RelationalReaderValueHadler?
+	SCM toGuileValue(const void* value, const std::type_info& typeInfo, TypeId type) {
 		switch (type) {
 			case TypeId::BOOLEAN:
-				scm_define(toGuileSymbol(name), toGuileBoolean(value));
-				break;
+			{
+				assert(typeInfo == typeid (boolean_t));
+				auto* typedValue = static_cast<const boolean_t*> (value);
+				return *typedValue ? SCM_BOOL_T : SCM_BOOL_F;
+			}
 			case TypeId::INTEGER:
-				scm_define(toGuileSymbol(name), toGuileInteger(value));
-				break;
+			{
+				assert(typeInfo == typeid (integer_t));
+				auto* typedValue = static_cast<const integer_t*> (value);
+				return scm_from_uint64(*typedValue);
+			}
 			case TypeId::STRING:
-				scm_define(toGuileSymbol(name), toGuileString(value));
-				break;
+			{
+				assert(typeInfo == typeid (string_t));
+				auto* typedValue = static_cast<const string_t*> (value);
+				return scm_from_locale_string(convertor.to_bytes(*typedValue).c_str());
+			}
 			default:
-				throw cli::RelpipeCLIException(L"Unsupported type in defineGuileVariable()", cli::CLI::EXIT_CODE_UNEXPECTED_ERROR);
+				throw cli::RelpipeCLIException(L"Unsupported type in toGuileValue()", cli::CLI::EXIT_CODE_UNEXPECTED_ERROR);
 		}
 	}
 
-	void undefineGuileVariable(const string_t& name, TypeId type, const string_t& value) {
+	void defineGuileVariable(const string_t& name, const void* value, const std::type_info& typeInfo, TypeId type) {
+		scm_define(toGuileSymbol(name), toGuileValue(value, typeInfo, type));
+	}
+
+	void undefineGuileVariable(const string_t& name) {
 		scm_define(toGuileSymbol(name), scm_make_undefined_variable()); // undefined != (define n)
 		// TODO: or use: scm_variable_unset_x() ?
 	}
 
+	void writeGuileValueToAttribute(const writer::AttributeMetadata& attribute) {
+		string_t variableName = a2v(attribute.attributeName);
+		SCM guileValue = scm_eval_string(toGuileValue(&variableName, typeid (variableName), TypeId::STRING));
+
+		switch (attribute.typeId) {
+			case writer::TypeId::BOOLEAN:
+			{
+				boolean_t value = scm_to_bool(guileValue);
+				return relationalWriter->writeAttribute(&value, typeid (value));
+			}
+			case writer::TypeId::INTEGER:
+			{
+				integer_t value = scm_to_uint64(guileValue);
+				return relationalWriter->writeAttribute(&value, typeid (value));
+			}
+			case writer::TypeId::STRING:
+			{
+				char* ch = scm_to_locale_string(guileValue);
+				string_t value = convertor.from_bytes(ch);
+				free(ch);
+				return relationalWriter->writeAttribute(&value, typeid (value));
+			}
+			default:
+				throw cli::RelpipeCLIException(L"Unsupported type in writeGuileValueToAttribute()", cli::CLI::EXIT_CODE_UNEXPECTED_ERROR);
+		}
+	}
+
 public:
 
 	GuileHandler(ostream& output, const vector<string_t>& arguments) {
@@ -107,44 +149,44 @@
 		// TODO: options and parser
 		if (arguments.size() == 2) {
 			relationNameRegEx = wregex(arguments[0]);
-			guileCode = arguments[1];
+			guileCodeWhereCondition = arguments[1];
 		} else {
 			throw cli::RelpipeCLIException(L"Usage: relpipe-tr-guile <relationNameRegExp> <whereConditionGuileCode>", cli::CLI::EXIT_CODE_UNKNOWN_COMMAND);
 		}
 	}
 
 	void startRelation(string_t name, vector<AttributeMetadata> attributes) override {
-		currentMetadata = attributes;
+		for (auto attribute : currentReaderMetadata) undefineGuileVariable(attribute.getAttributeName());
+		currentReaderMetadata = attributes;
 		// TODO: move to a reusable method (or use same metadata on both reader and writer side?)
-		vector<writer::AttributeMetadata> writerMetadata;
+		// TODO: allow structural changes during transformation
+		// TODO: clear Guile variables for attributes from previous relation
+		currentWriterMetadata.clear();
 		for (AttributeMetadata readerMetadata : attributes) {
-
-			writerMetadata.push_back({readerMetadata.getAttributeName(), relationalWriter->toTypeId(readerMetadata.getTypeName())});
+			currentWriterMetadata.push_back({readerMetadata.getAttributeName(), relationalWriter->toTypeId(readerMetadata.getTypeName())});
 		}
 
 		currentRecord.resize(attributes.size());
 		filterCurrentRelation = regex_match(name, relationNameRegEx);
 
-		relationalWriter->startRelation(name, writerMetadata, true);
+		relationalWriter->startRelation(name, currentWriterMetadata, true);
 	}
 
-	void attribute(const string_t& value) override {
+	void attribute(const void* value, const std::type_info& type) override {
 		if (filterCurrentRelation) {
-			currentRecord[currentAttributeIndex] = value;
-			defineGuileVariable(currentMetadata[currentAttributeIndex].getAttributeName(), currentMetadata[currentAttributeIndex].getTypeId(), value);
+			defineGuileVariable(a2v(currentReaderMetadata[currentAttributeIndex].getAttributeName()), value, type, currentReaderMetadata[currentAttributeIndex].getTypeId());
+
 			currentAttributeIndex++;
 
-			if (currentAttributeIndex > 0 && currentAttributeIndex % currentMetadata.size() == 0) {
-				// TODO: support also updates in Guile
-				includeCurrentRecord = scm_to_bool(scm_eval_string(toGuileString(guileCode)));
-				if (includeCurrentRecord) for (string_t v : currentRecord) relationalWriter->writeAttribute(v);
+			if (currentAttributeIndex > 0 && currentAttributeIndex % currentReaderMetadata.size() == 0) {
+				includeCurrentRecord = scm_to_bool(evalGuileCode(guileCodeWhereCondition));
+				if (includeCurrentRecord) for (auto attribute : currentWriterMetadata) writeGuileValueToAttribute(attribute);
 				includeCurrentRecord = false;
 			}
 
-			currentAttributeIndex = currentAttributeIndex % currentMetadata.size();
+			currentAttributeIndex = currentAttributeIndex % currentReaderMetadata.size();
 		} else {
-
-			relationalWriter->writeAttribute(value);
+			relationalWriter->writeAttribute(value, type);
 		}
 	}