relpipe-tr-guile → relpipe-tr-scheme v_0 v0.17
authorFrantišek Kučera <franta-hg@frantovo.cz>
Fri, 25 Sep 2020 01:59:16 +0200
branchv_0
changeset 33 e87c231afb77
parent 32 2354c9058fb6
child 34 6a16b36ab852
relpipe-tr-guile → relpipe-tr-scheme
CMakeLists.txt
bash-completion.sh
nbproject/configurations.xml
nbproject/project.xml
src/CLIParser.h
src/CMakeLists.txt
src/Configuration.h
src/GuileException.h
src/GuileHandler.h
src/SchemeException.h
src/SchemeHandler.h
src/relpipe-tr-guile.cpp
src/relpipe-tr-scheme.cpp
--- a/CMakeLists.txt	Sat Jun 06 01:50:45 2020 +0200
+++ b/CMakeLists.txt	Fri Sep 25 01:59:16 2020 +0200
@@ -13,6 +13,6 @@
 # You should have received a copy of the GNU General Public License
 # along with this program. If not, see <http://www.gnu.org/licenses/>.
 
-project (relpipe-tr-guile.cpp)
+project (relpipe-tr-scheme.cpp)
 cmake_minimum_required(VERSION 3.7.2)
 add_subdirectory (src)
--- a/bash-completion.sh	Sat Jun 06 01:50:45 2020 +0200
+++ b/bash-completion.sh	Fri Sep 25 01:59:16 2020 +0200
@@ -13,7 +13,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program. If not, see <http://www.gnu.org/licenses/>.
 
-_relpipe_tr_guile_completion() {
+_relpipe_tr_scheme_completion() {
 	local w0 w1 w2
 
 	COMPREPLY=()
@@ -57,5 +57,5 @@
 	fi
 }
 
-complete -F _relpipe_tr_guile_completion relpipe-tr-guile
-complete -F _relpipe_tr_guile_completion relpipe-in-guile
+complete -F _relpipe_tr_scheme_completion relpipe-tr-scheme
+complete -F _relpipe_tr_scheme_completion relpipe-in-scheme
--- a/nbproject/configurations.xml	Sat Jun 06 01:50:45 2020 +0200
+++ b/nbproject/configurations.xml	Fri Sep 25 01:59:16 2020 +0200
@@ -42,7 +42,7 @@
   <logicalFolder name="root" displayName="root" projectFiles="true" kind="ROOT">
     <df root="." name="0">
       <df name="src">
-        <in>relpipe-tr-guile.cpp</in>
+        <in>relpipe-tr-scheme.cpp</in>
       </df>
     </df>
     <logicalFolder name="ExternalFiles"
@@ -76,11 +76,12 @@
           <buildCommandWorkingDir>build/Debug</buildCommandWorkingDir>
           <buildCommand>${MAKE} -f Makefile</buildCommand>
           <cleanCommand>${MAKE} -f Makefile clean</cleanCommand>
-          <executablePath>build/Debug/src/relpipe-tr-guile</executablePath>
+          <executablePath>build/Debug/src/relpipe-tr-scheme</executablePath>
           <ccTool>
             <incDir>
               <pElem>../relpipe-lib-reader.cpp/include</pElem>
               <pElem>../relpipe-lib-writer.cpp/include</pElem>
+              <pElem>../relpipe-lib-common.cpp/include</pElem>
               <pElem>../relpipe-lib-cli.cpp/include</pElem>
               <pElem>/usr/include/guile/2.2</pElem>
               <pElem>build/Debug/src</pElem>
@@ -93,7 +94,7 @@
           <preBuildFirst>true</preBuildFirst>
         </preBuild>
       </makefileType>
-      <item path="src/relpipe-tr-guile.cpp" ex="false" tool="1" flavor2="0">
+      <item path="src/relpipe-tr-scheme.cpp" ex="false" tool="1" flavor2="0">
         <ccTool flags="0">
         </ccTool>
       </item>
@@ -133,7 +134,7 @@
           <preBuildFirst>true</preBuildFirst>
         </preBuild>
       </makefileType>
-      <item path="src/relpipe-tr-guile.cpp" ex="false" tool="1" flavor2="0">
+      <item path="src/relpipe-tr-scheme.cpp" ex="false" tool="1" flavor2="0">
         <ccTool flags="0">
         </ccTool>
       </item>
--- a/nbproject/project.xml	Sat Jun 06 01:50:45 2020 +0200
+++ b/nbproject/project.xml	Fri Sep 25 01:59:16 2020 +0200
@@ -42,7 +42,7 @@
     <type>org.netbeans.modules.cnd.makeproject</type>
     <configuration>
         <data xmlns="http://www.netbeans.org/ns/make-project/1">
-            <name>relpipe-tr-guile.cpp</name>
+            <name>relpipe-tr-scheme.cpp</name>
             <c-extensions/>
             <cpp-extensions>cpp</cpp-extensions>
             <header-extensions>h</header-extensions>
--- a/src/CLIParser.h	Sat Jun 06 01:50:45 2020 +0200
+++ b/src/CLIParser.h	Fri Sep 25 01:59:16 2020 +0200
@@ -26,7 +26,7 @@
 
 namespace relpipe {
 namespace tr {
-namespace guile {
+namespace scheme {
 
 class CLIParser {
 private:
@@ -75,11 +75,11 @@
 		for (int i = 0; i < arguments.size();) {
 			string_t option = readNext(arguments, i);
 
-			if (option == OPTION_BEFORE_RECORDS) currentRelation.guileBeforeRecords = readNext(arguments, i);
-			else if (option == OPTION_AFTER_RECORDS) currentRelation.guileAfterRecords = readNext(arguments, i);
-			else if (option == OPTION_FOR_EACH) currentRelation.guileForEach = readNext(arguments, i);
-			else if (option == OPTION_WHERE) currentRelation.guileWhere = readNext(arguments, i);
-			else if (option == OPTION_HAS_MORE_RECORDS) currentRelation.guileHasMoreRecords = readNext(arguments, i);
+			if (option == OPTION_BEFORE_RECORDS) currentRelation.schemeBeforeRecords = readNext(arguments, i);
+			else if (option == OPTION_AFTER_RECORDS) currentRelation.schemeAfterRecords = readNext(arguments, i);
+			else if (option == OPTION_FOR_EACH) currentRelation.schemeForEach = readNext(arguments, i);
+			else if (option == OPTION_WHERE) currentRelation.schemeWhere = readNext(arguments, i);
+			else if (option == OPTION_HAS_MORE_RECORDS) currentRelation.schemeHasMoreRecords = readNext(arguments, i);
 			else if (option == OPTION_DROP) currentRelation.drop = true;
 			else if (option == OPTION_INPUT_ATTRIBUTES_APPEND) currentRelation.inputAttributesAppend = true;
 			else if (option == OPTION_INPUT_ATTRIBUTES_PREPEND) currentRelation.inputAttributesPrepend = true;
--- a/src/CMakeLists.txt	Sat Jun 06 01:50:45 2020 +0200
+++ b/src/CMakeLists.txt	Fri Sep 25 01:59:16 2020 +0200
@@ -13,7 +13,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program. If not, see <http://www.gnu.org/licenses/>.
 
-set(EXECUTABLE_FILE "relpipe-tr-guile")
+set(EXECUTABLE_FILE "relpipe-tr-scheme")
 
 # Relpipe libraries:
 INCLUDE(FindPkgConfig)
@@ -29,7 +29,7 @@
 # Executable output:
 add_executable(
 	${EXECUTABLE_FILE}
-	relpipe-tr-guile.cpp
+	relpipe-tr-scheme.cpp
 )
 
 # Link libraries:
--- a/src/Configuration.h	Sat Jun 06 01:50:45 2020 +0200
+++ b/src/Configuration.h	Fri Sep 25 01:59:16 2020 +0200
@@ -24,7 +24,7 @@
 
 namespace relpipe {
 namespace tr {
-namespace guile {
+namespace scheme {
 
 class DefinitionRecipe {
 public:
@@ -44,19 +44,19 @@
 	}
 
 	relpipe::writer::string_t relation;
-	relpipe::writer::string_t guileBeforeRecords;
-	relpipe::writer::string_t guileAfterRecords;
-	relpipe::writer::string_t guileForEach;
-	relpipe::writer::string_t guileWhere;
-	relpipe::writer::string_t guileHasMoreRecords;
+	relpipe::writer::string_t schemeBeforeRecords;
+	relpipe::writer::string_t schemeAfterRecords;
+	relpipe::writer::string_t schemeForEach;
+	relpipe::writer::string_t schemeWhere;
+	relpipe::writer::string_t schemeHasMoreRecords;
 	
 	/**
-	 * If true, additional relation will be generated: mapping between relpipe attribute names and Guile variable names
+	 * If true, additional relation will be generated: mapping between relpipe attribute names and Scheme variable names
 	 */
 	bool debugVariableMapping = false;
 
 	/**
-	 * If true, Guile code will be executed, but no output will be generated.
+	 * If true, Scheme code will be executed, but no output will be generated.
 	 */
 	bool drop = false;
 
@@ -94,9 +94,9 @@
 	 */
 	std::vector<DefinitionRecipe> definitions;
 
-	// TODO: --create t2 3 a integer b boolean '…guile…' – allow creating new relations? Or allow calling startRelation() and attribute() from Guile?
-	// TODO: --has-more-relations '…guile…' – called after all relations … ?
-	// TODO: --dynamic-output-attributes '…guile…' – used instead or together with --output-attribute
+	// TODO: --create t2 3 a integer b boolean '…scheme…' – allow creating new relations? Or allow calling startRelation() and attribute() from Scheme?
+	// TODO: --has-more-relations '…scheme…' – called after all relations … ?
+	// TODO: --dynamic-output-attributes '…scheme…' – used instead or together with --output-attribute
 
 	virtual ~Configuration() {
 	}
--- a/src/GuileException.h	Sat Jun 06 01:50:45 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-/**
- * Relational pipes
- * Copyright © 2019 František Kučera (Frantovo.cz, GlobalCode.info)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#pragma once
-
-#include <string>
-
-using namespace std;
-
-namespace relpipe {
-namespace tr {
-namespace guile {
-
-class GuileException {
-private:
-	wstring message;
-public:
-
-	GuileException(wstring message) : message(message) {
-	}
-
-	wstring getMessge() {
-		return message;
-	}
-
-};
-
-}
-}
-}
\ No newline at end of file
--- a/src/GuileHandler.h	Sat Jun 06 01:50:45 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,321 +0,0 @@
-/**
- * Relational pipes
- * Copyright © 2019 František Kučera (Frantovo.cz, GlobalCode.info)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#pragma once
-
-#include <memory>
-#include <string>
-#include <vector>
-#include <map>
-#include <iostream>
-#include <sstream>
-#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/RelationalReaderValueHandler.h>
-#include <relpipe/reader/handlers/AttributeMetadata.h>
-
-#include <relpipe/writer/Factory.h>
-
-#include <relpipe/cli/RelpipeCLIException.h>
-
-#include "Configuration.h"
-#include "GuileException.h"
-
-namespace relpipe {
-namespace tr {
-namespace guile {
-
-using namespace std;
-using namespace relpipe;
-using namespace relpipe::reader;
-using namespace relpipe::reader::handlers;
-
-class GuileHandler : public RelationalReaderValueHandler {
-private:
-	std::wstring_convert<codecvt_utf8<wchar_t>> convertor; // TODO: support also other encodings or use always UTF-8 between C++ and Guile
-
-	Configuration configuration;
-	writer::RelationalWriter* relationalWriter;
-
-	RelationConfiguration* currentRelationConfiguration = nullptr;
-	vector<AttributeMetadata> currentReaderMetadata;
-	vector<writer::AttributeMetadata> currentWriterMetadata;
-	std::map<string_t, string_t> currenVariablesMapping;
-	integer_t currentAttributeIndex = 0;
-	boolean_t includeCurrentRecord = false;
-
-	void add(vector<AttributeMetadata>& readerAttributes, vector<writer::AttributeMetadata>& writerAttributes) {
-		for (AttributeMetadata readerAttributes : readerAttributes)
-			writerAttributes.push_back({
-				readerAttributes.getAttributeName(),
-				relationalWriter->toTypeId(readerAttributes.getTypeName())
-			});
-	}
-
-	void generateVariableMappings() {
-		currenVariablesMapping.clear();
-		for (AttributeMetadata m : currentReaderMetadata) currenVariablesMapping[m.getAttributeName()] = L"";
-		for (writer::AttributeMetadata m : currentWriterMetadata) currenVariablesMapping[m.attributeName] = L"";
-
-		for (std::pair<string_t, string_t> m : currenVariablesMapping) {
-			currenVariablesMapping[m.first] = escapeAwkVariableName(m.first);
-		}
-	}
-
-	/**
-	 * @param attributeName name from relational pipe
-	 * @return variable name in Guile
-	 */
-	string_t a2v(const string_t& attributeName) {
-		if (currenVariablesMapping.find(attributeName) != currenVariablesMapping.end()) return currenVariablesMapping[attributeName];
-		else throw GuileException(L"Unable to find value in currenVariablesMapping");
-	}
-
-	template <typename K, typename V> bool containsValue(std::map<K, V> map, V value) { // TODO: common function (Guile, AWK)
-		for (std::pair<K, V> p : map) if (p.second == value) return true;
-		return false;
-	}
-
-	string_t escapeAwkVariableName(const string_t& attributeName, bool addPrefix = true) {
-		std::wregex badCharacters(L"\\s");
-		string_t name = std::regex_replace(attributeName, badCharacters, L"-");
-
-		if (addPrefix) name = L"$" + name; // $ = standard attribute-variable prefix
-
-		if (containsValue(currenVariablesMapping, name)) return escapeAwkVariableName(L"$" + name, false); // $ = different prefix added to distinguish two attributes with ambiguous names
-		else return name;
-
-	}
-
-	void debugVariableMapping(const string_t& relationName) {
-		relationalWriter->startRelation(relationName + L".variableMapping",{
-			{L"attribute", writer::TypeId::STRING},
-			{L"variable", writer::TypeId::STRING},
-		}, true);
-
-		for (std::pair<string_t, string_t> m : currenVariablesMapping) {
-			relationalWriter->writeAttribute(m.first);
-			relationalWriter->writeAttribute(m.second);
-		}
-	}
-
-	SCM toGuileSymbol(const string_t& name) {
-		return scm_string_to_symbol(scm_from_locale_string(convertor.to_bytes(name).c_str()));
-	}
-
-	/**
-	 * @param code guile source code e.g. (+ 1 2 3) or #t
-	 * @param defaultReturnValue is returned if code is empty
-	 * @return result of code execution or defaultReturnValue
-	 */
-	SCM evalGuileCode(const string_t& code, SCM defaultReturnValue = SCM_BOOL_F) {
-		if (code.size()) return scm_eval_string(toGuileValue(&code, typeid (string_t), TypeId::STRING));
-		else return defaultReturnValue;
-	}
-
-	SCM toGuileValue(const void* value, const std::type_info& typeInfo, TypeId type) {
-		switch (type) {
-			case TypeId::BOOLEAN:
-			{
-				assert(typeInfo == typeid (boolean_t));
-				auto* typedValue = static_cast<const boolean_t*> (value);
-				return *typedValue ? SCM_BOOL_T : SCM_BOOL_F;
-			}
-			case TypeId::INTEGER:
-			{
-				assert(typeInfo == typeid (integer_t));
-				auto* typedValue = static_cast<const integer_t*> (value);
-				return scm_from_int64(*typedValue);
-			}
-			case TypeId::STRING:
-			{
-				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 toGuileValue()", cli::CLI::EXIT_CODE_UNEXPECTED_ERROR);
-		}
-	}
-
-	void defineGuileVariable(const string_t& name, const void* value, const std::type_info& typeInfo, TypeId type) {
-		scm_define(toGuileSymbol(name), toGuileValue(value, typeInfo, type));
-	}
-
-	/**
-	 * TODO: use a common method
-	 */
-	bool parseBoolean(const string_t& value) {
-		if (value == L"true") return true;
-		else if (value == L"false") return false;
-		else throw relpipe::cli::RelpipeCLIException(L"Unable to parse boolean value: " + value + L" (expecting true or false)", relpipe::cli::CLI::EXIT_CODE_BAD_CLI_ARGUMENTS);
-	}
-
-	void defineGuileVariable(const DefinitionRecipe& definition) {
-		switch (relationalWriter->toTypeId(definition.type)) {
-			case writer::TypeId::BOOLEAN:
-			{
-				boolean_t value = parseBoolean(definition.value);
-				defineGuileVariable(definition.name, &value, typeid (value), TypeId::BOOLEAN);
-				break;
-			}
-			case writer::TypeId::INTEGER:
-			{
-				integer_t value = stol(definition.value);
-				defineGuileVariable(definition.name, &value, typeid (value), TypeId::INTEGER);
-				break;
-			}
-			case writer::TypeId::STRING:
-			{
-				defineGuileVariable(definition.name, &definition.value, typeid (definition.value), TypeId::STRING);
-				break;
-			}
-			default:
-				throw cli::RelpipeCLIException(L"Unsupported type in defineGuileVariable(): " + definition.type, cli::CLI::EXIT_CODE_UNEXPECTED_ERROR);
-		}
-	}
-
-	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_int64(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);
-		}
-	}
-
-	/**
-	 * Read from the Guile variables and write to relational output stream.
-	 */
-	void writeCurrentRecord() {
-		for (auto attribute : currentWriterMetadata) writeGuileValueToAttribute(attribute);
-	}
-
-	void writeMoreRecords() {
-		while (scm_to_bool(evalGuileCode(currentRelationConfiguration->guileHasMoreRecords, SCM_BOOL_F))) writeCurrentRecord();
-	}
-
-public:
-
-	GuileHandler(writer::RelationalWriter* relationalWriter, Configuration& configuration) : relationalWriter(relationalWriter), configuration(configuration) {
-	}
-
-	void startRelation(string_t name, vector<AttributeMetadata> attributes) override {
-		if (currentRelationConfiguration) {
-			evalGuileCode(currentRelationConfiguration->guileAfterRecords);
-			writeMoreRecords();
-			for (DefinitionRecipe definition : currentRelationConfiguration->definitions) undefineGuileVariable(definition.name);
-		}
-		for (auto attribute : currentReaderMetadata) undefineGuileVariable(attribute.getAttributeName());
-
-		for (DefinitionRecipe definition : configuration.definitions) defineGuileVariable(definition);
-
-		currentRelationConfiguration = nullptr;
-		for (int i = 0; i < configuration.relationConfigurations.size(); i++) {
-			if (regex_match(name, wregex(configuration.relationConfigurations[i].relation))) {
-				currentRelationConfiguration = &configuration.relationConfigurations[i];
-				for (DefinitionRecipe definition : currentRelationConfiguration->definitions) defineGuileVariable(definition);
-				break; // it there are multiple matches, only the first configuration is used
-			}
-		}
-
-		currentReaderMetadata = attributes;
-		// TODO: move to a reusable method (or use same metadata on both reader and writer side?)
-		currentWriterMetadata.clear();
-		if (currentRelationConfiguration && currentRelationConfiguration->writerMetadata.size()) {
-			if (currentRelationConfiguration->inputAttributesPrepend) add(currentReaderMetadata, currentWriterMetadata);
-			currentWriterMetadata.insert(currentWriterMetadata.end(), currentRelationConfiguration->writerMetadata.begin(), currentRelationConfiguration->writerMetadata.end());
-			if (currentRelationConfiguration->inputAttributesAppend) add(currentReaderMetadata, currentWriterMetadata);
-		} else {
-			add(currentReaderMetadata, currentWriterMetadata);
-		}
-
-		generateVariableMappings();
-
-		if (currentRelationConfiguration && currentRelationConfiguration->debugVariableMapping) debugVariableMapping(name);
-
-		if (!currentRelationConfiguration || !currentRelationConfiguration->drop) relationalWriter->startRelation(name, currentWriterMetadata, true);
-
-		if (currentRelationConfiguration) {
-			// TODO: better variable name, object, function?
-			defineGuileVariable(L"relpipe-relation-name", &name, typeid (name), TypeId::STRING);
-			evalGuileCode(currentRelationConfiguration->guileBeforeRecords);
-		}
-	}
-
-	void attribute(const void* value, const std::type_info& type) override {
-		if (currentRelationConfiguration) {
-			defineGuileVariable(a2v(currentReaderMetadata[currentAttributeIndex].getAttributeName()), value, type, currentReaderMetadata[currentAttributeIndex].getTypeId());
-
-			currentAttributeIndex++;
-
-			// TODO: > 0 ?:
-			if (currentAttributeIndex > 0 && currentAttributeIndex % currentReaderMetadata.size() == 0) {
-				evalGuileCode(currentRelationConfiguration->guileForEach);
-				includeCurrentRecord = scm_to_bool(evalGuileCode(currentRelationConfiguration->guileWhere, SCM_BOOL_T));
-				if (includeCurrentRecord && !currentRelationConfiguration->drop) writeCurrentRecord();
-				includeCurrentRecord = false;
-				writeMoreRecords();
-			}
-
-			currentAttributeIndex = currentAttributeIndex % currentReaderMetadata.size();
-		} else {
-			relationalWriter->writeAttribute(value, type);
-		}
-	}
-
-	void endOfPipe() {
-		if (currentRelationConfiguration) {
-			evalGuileCode(currentRelationConfiguration->guileAfterRecords);
-			writeMoreRecords();
-		}
-	}
-
-};
-
-}
-}
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/SchemeException.h	Fri Sep 25 01:59:16 2020 +0200
@@ -0,0 +1,43 @@
+/**
+ * Relational pipes
+ * Copyright © 2019 František Kučera (Frantovo.cz, GlobalCode.info)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <string>
+
+using namespace std;
+
+namespace relpipe {
+namespace tr {
+namespace scheme {
+
+class SchemeException {
+private:
+	wstring message;
+public:
+
+	SchemeException(wstring message) : message(message) {
+	}
+
+	wstring getMessge() {
+		return message;
+	}
+
+};
+
+}
+}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/SchemeHandler.h	Fri Sep 25 01:59:16 2020 +0200
@@ -0,0 +1,321 @@
+/**
+ * Relational pipes
+ * Copyright © 2019 František Kučera (Frantovo.cz, GlobalCode.info)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <memory>
+#include <string>
+#include <vector>
+#include <map>
+#include <iostream>
+#include <sstream>
+#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/RelationalReaderValueHandler.h>
+#include <relpipe/reader/handlers/AttributeMetadata.h>
+
+#include <relpipe/writer/Factory.h>
+
+#include <relpipe/cli/RelpipeCLIException.h>
+
+#include "Configuration.h"
+#include "SchemeException.h"
+
+namespace relpipe {
+namespace tr {
+namespace scheme {
+
+using namespace std;
+using namespace relpipe;
+using namespace relpipe::reader;
+using namespace relpipe::reader::handlers;
+
+class SchemeHandler : public RelationalReaderValueHandler {
+private:
+	std::wstring_convert<codecvt_utf8<wchar_t>> convertor; // TODO: support also other encodings or use always UTF-8 between C++ and Scheme
+
+	Configuration configuration;
+	writer::RelationalWriter* relationalWriter;
+
+	RelationConfiguration* currentRelationConfiguration = nullptr;
+	vector<AttributeMetadata> currentReaderMetadata;
+	vector<writer::AttributeMetadata> currentWriterMetadata;
+	std::map<string_t, string_t> currenVariablesMapping;
+	integer_t currentAttributeIndex = 0;
+	boolean_t includeCurrentRecord = false;
+
+	void add(vector<AttributeMetadata>& readerAttributes, vector<writer::AttributeMetadata>& writerAttributes) {
+		for (AttributeMetadata readerAttributes : readerAttributes)
+			writerAttributes.push_back({
+				readerAttributes.getAttributeName(),
+				relationalWriter->toTypeId(readerAttributes.getTypeName())
+			});
+	}
+
+	void generateVariableMappings() {
+		currenVariablesMapping.clear();
+		for (AttributeMetadata m : currentReaderMetadata) currenVariablesMapping[m.getAttributeName()] = L"";
+		for (writer::AttributeMetadata m : currentWriterMetadata) currenVariablesMapping[m.attributeName] = L"";
+
+		for (std::pair<string_t, string_t> m : currenVariablesMapping) {
+			currenVariablesMapping[m.first] = escapeAwkVariableName(m.first);
+		}
+	}
+
+	/**
+	 * @param attributeName name from relational pipe
+	 * @return variable name in Scheme
+	 */
+	string_t a2v(const string_t& attributeName) {
+		if (currenVariablesMapping.find(attributeName) != currenVariablesMapping.end()) return currenVariablesMapping[attributeName];
+		else throw SchemeException(L"Unable to find value in currenVariablesMapping");
+	}
+
+	template <typename K, typename V> bool containsValue(std::map<K, V> map, V value) { // TODO: common function (Scheme, AWK)
+		for (std::pair<K, V> p : map) if (p.second == value) return true;
+		return false;
+	}
+
+	string_t escapeAwkVariableName(const string_t& attributeName, bool addPrefix = true) {
+		std::wregex badCharacters(L"\\s");
+		string_t name = std::regex_replace(attributeName, badCharacters, L"-");
+
+		if (addPrefix) name = L"$" + name; // $ = standard attribute-variable prefix
+
+		if (containsValue(currenVariablesMapping, name)) return escapeAwkVariableName(L"$" + name, false); // $ = different prefix added to distinguish two attributes with ambiguous names
+		else return name;
+
+	}
+
+	void debugVariableMapping(const string_t& relationName) {
+		relationalWriter->startRelation(relationName + L".variableMapping",{
+			{L"attribute", writer::TypeId::STRING},
+			{L"variable", writer::TypeId::STRING},
+		}, true);
+
+		for (std::pair<string_t, string_t> m : currenVariablesMapping) {
+			relationalWriter->writeAttribute(m.first);
+			relationalWriter->writeAttribute(m.second);
+		}
+	}
+
+	SCM toSchemeSymbol(const string_t& name) {
+		return scm_string_to_symbol(scm_from_locale_string(convertor.to_bytes(name).c_str()));
+	}
+
+	/**
+	 * @param code scheme source code e.g. (+ 1 2 3) or #t
+	 * @param defaultReturnValue is returned if code is empty
+	 * @return result of code execution or defaultReturnValue
+	 */
+	SCM evalSchemeCode(const string_t& code, SCM defaultReturnValue = SCM_BOOL_F) {
+		if (code.size()) return scm_eval_string(toSchemeValue(&code, typeid (string_t), TypeId::STRING));
+		else return defaultReturnValue;
+	}
+
+	SCM toSchemeValue(const void* value, const std::type_info& typeInfo, TypeId type) {
+		switch (type) {
+			case TypeId::BOOLEAN:
+			{
+				assert(typeInfo == typeid (boolean_t));
+				auto* typedValue = static_cast<const boolean_t*> (value);
+				return *typedValue ? SCM_BOOL_T : SCM_BOOL_F;
+			}
+			case TypeId::INTEGER:
+			{
+				assert(typeInfo == typeid (integer_t));
+				auto* typedValue = static_cast<const integer_t*> (value);
+				return scm_from_int64(*typedValue);
+			}
+			case TypeId::STRING:
+			{
+				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 toSchemeValue()", cli::CLI::EXIT_CODE_UNEXPECTED_ERROR);
+		}
+	}
+
+	void defineSchemeVariable(const string_t& name, const void* value, const std::type_info& typeInfo, TypeId type) {
+		scm_define(toSchemeSymbol(name), toSchemeValue(value, typeInfo, type));
+	}
+
+	/**
+	 * TODO: use a common method
+	 */
+	bool parseBoolean(const string_t& value) {
+		if (value == L"true") return true;
+		else if (value == L"false") return false;
+		else throw relpipe::cli::RelpipeCLIException(L"Unable to parse boolean value: " + value + L" (expecting true or false)", relpipe::cli::CLI::EXIT_CODE_BAD_CLI_ARGUMENTS);
+	}
+
+	void defineSchemeVariable(const DefinitionRecipe& definition) {
+		switch (relationalWriter->toTypeId(definition.type)) {
+			case writer::TypeId::BOOLEAN:
+			{
+				boolean_t value = parseBoolean(definition.value);
+				defineSchemeVariable(definition.name, &value, typeid (value), TypeId::BOOLEAN);
+				break;
+			}
+			case writer::TypeId::INTEGER:
+			{
+				integer_t value = stol(definition.value);
+				defineSchemeVariable(definition.name, &value, typeid (value), TypeId::INTEGER);
+				break;
+			}
+			case writer::TypeId::STRING:
+			{
+				defineSchemeVariable(definition.name, &definition.value, typeid (definition.value), TypeId::STRING);
+				break;
+			}
+			default:
+				throw cli::RelpipeCLIException(L"Unsupported type in defineSchemeVariable(): " + definition.type, cli::CLI::EXIT_CODE_UNEXPECTED_ERROR);
+		}
+	}
+
+	void undefineSchemeVariable(const string_t& name) {
+		scm_define(toSchemeSymbol(name), scm_make_undefined_variable()); // undefined != (define n)
+		// TODO: or use: scm_variable_unset_x() ?
+	}
+
+	void writeSchemeValueToAttribute(const writer::AttributeMetadata& attribute) {
+		string_t variableName = a2v(attribute.attributeName);
+		SCM schemeValue = scm_eval_string(toSchemeValue(&variableName, typeid (variableName), TypeId::STRING));
+
+		switch (attribute.typeId) {
+			case writer::TypeId::BOOLEAN:
+			{
+				boolean_t value = scm_to_bool(schemeValue);
+				return relationalWriter->writeAttribute(&value, typeid (value));
+			}
+			case writer::TypeId::INTEGER:
+			{
+				integer_t value = scm_to_int64(schemeValue);
+				return relationalWriter->writeAttribute(&value, typeid (value));
+			}
+			case writer::TypeId::STRING:
+			{
+				char* ch = scm_to_locale_string(schemeValue);
+				string_t value = convertor.from_bytes(ch);
+				free(ch);
+				return relationalWriter->writeAttribute(&value, typeid (value));
+			}
+			default:
+				throw cli::RelpipeCLIException(L"Unsupported type in writeSchemeValueToAttribute()", cli::CLI::EXIT_CODE_UNEXPECTED_ERROR);
+		}
+	}
+
+	/**
+	 * Read from the Scheme variables and write to relational output stream.
+	 */
+	void writeCurrentRecord() {
+		for (auto attribute : currentWriterMetadata) writeSchemeValueToAttribute(attribute);
+	}
+
+	void writeMoreRecords() {
+		while (scm_to_bool(evalSchemeCode(currentRelationConfiguration->schemeHasMoreRecords, SCM_BOOL_F))) writeCurrentRecord();
+	}
+
+public:
+
+	SchemeHandler(writer::RelationalWriter* relationalWriter, Configuration& configuration) : relationalWriter(relationalWriter), configuration(configuration) {
+	}
+
+	void startRelation(string_t name, vector<AttributeMetadata> attributes) override {
+		if (currentRelationConfiguration) {
+			evalSchemeCode(currentRelationConfiguration->schemeAfterRecords);
+			writeMoreRecords();
+			for (DefinitionRecipe definition : currentRelationConfiguration->definitions) undefineSchemeVariable(definition.name);
+		}
+		for (auto attribute : currentReaderMetadata) undefineSchemeVariable(attribute.getAttributeName());
+
+		for (DefinitionRecipe definition : configuration.definitions) defineSchemeVariable(definition);
+
+		currentRelationConfiguration = nullptr;
+		for (int i = 0; i < configuration.relationConfigurations.size(); i++) {
+			if (regex_match(name, wregex(configuration.relationConfigurations[i].relation))) {
+				currentRelationConfiguration = &configuration.relationConfigurations[i];
+				for (DefinitionRecipe definition : currentRelationConfiguration->definitions) defineSchemeVariable(definition);
+				break; // it there are multiple matches, only the first configuration is used
+			}
+		}
+
+		currentReaderMetadata = attributes;
+		// TODO: move to a reusable method (or use same metadata on both reader and writer side?)
+		currentWriterMetadata.clear();
+		if (currentRelationConfiguration && currentRelationConfiguration->writerMetadata.size()) {
+			if (currentRelationConfiguration->inputAttributesPrepend) add(currentReaderMetadata, currentWriterMetadata);
+			currentWriterMetadata.insert(currentWriterMetadata.end(), currentRelationConfiguration->writerMetadata.begin(), currentRelationConfiguration->writerMetadata.end());
+			if (currentRelationConfiguration->inputAttributesAppend) add(currentReaderMetadata, currentWriterMetadata);
+		} else {
+			add(currentReaderMetadata, currentWriterMetadata);
+		}
+
+		generateVariableMappings();
+
+		if (currentRelationConfiguration && currentRelationConfiguration->debugVariableMapping) debugVariableMapping(name);
+
+		if (!currentRelationConfiguration || !currentRelationConfiguration->drop) relationalWriter->startRelation(name, currentWriterMetadata, true);
+
+		if (currentRelationConfiguration) {
+			// TODO: better variable name, object, function?
+			defineSchemeVariable(L"relpipe-relation-name", &name, typeid (name), TypeId::STRING);
+			evalSchemeCode(currentRelationConfiguration->schemeBeforeRecords);
+		}
+	}
+
+	void attribute(const void* value, const std::type_info& type) override {
+		if (currentRelationConfiguration) {
+			defineSchemeVariable(a2v(currentReaderMetadata[currentAttributeIndex].getAttributeName()), value, type, currentReaderMetadata[currentAttributeIndex].getTypeId());
+
+			currentAttributeIndex++;
+
+			// TODO: > 0 ?:
+			if (currentAttributeIndex > 0 && currentAttributeIndex % currentReaderMetadata.size() == 0) {
+				evalSchemeCode(currentRelationConfiguration->schemeForEach);
+				includeCurrentRecord = scm_to_bool(evalSchemeCode(currentRelationConfiguration->schemeWhere, SCM_BOOL_T));
+				if (includeCurrentRecord && !currentRelationConfiguration->drop) writeCurrentRecord();
+				includeCurrentRecord = false;
+				writeMoreRecords();
+			}
+
+			currentAttributeIndex = currentAttributeIndex % currentReaderMetadata.size();
+		} else {
+			relationalWriter->writeAttribute(value, type);
+		}
+	}
+
+	void endOfPipe() {
+		if (currentRelationConfiguration) {
+			evalSchemeCode(currentRelationConfiguration->schemeAfterRecords);
+			writeMoreRecords();
+		}
+	}
+
+};
+
+}
+}
+}
--- a/src/relpipe-tr-guile.cpp	Sat Jun 06 01:50:45 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-/**
- * Relational pipes
- * Copyright © 2019 František Kučera (Frantovo.cz, GlobalCode.info)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <cstdio>
-#include <cstdlib>
-#include <memory>
-
-#include <relpipe/cli/CLI.h>
-#include <relpipe/cli/RelpipeCLIException.h>
-
-#include <relpipe/reader/Factory.h>
-#include <relpipe/reader/RelationalReader.h>
-#include <relpipe/reader/RelpipeReaderException.h>
-
-#include <relpipe/writer/RelationalWriter.h>
-#include <relpipe/writer/RelpipeWriterException.h>
-#include <relpipe/writer/Factory.h>
-#include <relpipe/writer/TypeId.h>
-
-#include "GuileHandler.h"
-#include "CLIParser.h"
-
-using namespace relpipe::cli;
-using namespace relpipe::tr::guile;
-
-static void relpipeMain(void *closure, int argc, char **argv) {
-	setlocale(LC_ALL, "");
-	CLI::untieStdIO();
-	CLI cli(argc, argv);
-
-	int resultCode = CLI::EXIT_CODE_UNEXPECTED_ERROR;
-
-	try {
-		CLIParser cliParser;
-		Configuration configuration = cliParser.parse(cli.arguments());
-		std::shared_ptr<reader::RelationalReader> reader(reader::Factory::create(std::cin));
-		std::shared_ptr<writer::RelationalWriter> writer(writer::Factory::create(std::cout));
-		GuileHandler handler(writer.get(), configuration);
-		reader->addHandler(&handler);
-		reader->process();
-
-		resultCode = CLI::EXIT_CODE_SUCCESS;
-
-	} catch (RelpipeCLIException& e) {
-		fwprintf(stderr, L"Caught CLI exception: %ls\n", e.getMessge().c_str());
-		fwprintf(stderr, L"Debug: Input stream: eof=%ls, lastRead=%d\n", (cin.eof() ? L"true" : L"false"), cin.gcount());
-		resultCode = e.getExitCode();
-	} catch (RelpipeReaderException& e) {
-		fwprintf(stderr, L"Caught Reader exception: %ls\n", e.getMessge().c_str());
-		fwprintf(stderr, L"Debug: Input stream: eof=%ls, lastRead=%d\n", (cin.eof() ? L"true" : L"false"), cin.gcount());
-		resultCode = CLI::EXIT_CODE_DATA_ERROR;
-	} catch (GuileException& e) {
-		fwprintf(stderr, L"Caught Guile exception: %ls\n", e.getMessge().c_str());
-		fwprintf(stderr, L"Debug: Input stream: eof=%ls, lastRead=%d\n", (cin.eof() ? L"true" : L"false"), cin.gcount());
-		resultCode = CLI::EXIT_CODE_UNEXPECTED_ERROR;
-	}
-
-	exit(resultCode);
-}
-
-int main(int argc, char**argv) {
-	scm_boot_guile(argc, argv, relpipeMain, nullptr);
-	return 222; // never reached – see exit(resultCode) above
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/relpipe-tr-scheme.cpp	Fri Sep 25 01:59:16 2020 +0200
@@ -0,0 +1,78 @@
+/**
+ * Relational pipes
+ * Copyright © 2019 František Kučera (Frantovo.cz, GlobalCode.info)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <cstdio>
+#include <cstdlib>
+#include <memory>
+
+#include <relpipe/cli/CLI.h>
+#include <relpipe/cli/RelpipeCLIException.h>
+
+#include <relpipe/reader/Factory.h>
+#include <relpipe/reader/RelationalReader.h>
+#include <relpipe/reader/RelpipeReaderException.h>
+
+#include <relpipe/writer/RelationalWriter.h>
+#include <relpipe/writer/RelpipeWriterException.h>
+#include <relpipe/writer/Factory.h>
+#include <relpipe/writer/TypeId.h>
+
+#include "SchemeHandler.h"
+#include "CLIParser.h"
+
+using namespace relpipe::cli;
+using namespace relpipe::tr::scheme;
+
+static void relpipeMain(void *closure, int argc, char **argv) {
+	setlocale(LC_ALL, "");
+	CLI::untieStdIO();
+	CLI cli(argc, argv);
+
+	int resultCode = CLI::EXIT_CODE_UNEXPECTED_ERROR;
+
+	try {
+		CLIParser cliParser;
+		Configuration configuration = cliParser.parse(cli.arguments());
+		std::shared_ptr<reader::RelationalReader> reader(reader::Factory::create(std::cin));
+		std::shared_ptr<writer::RelationalWriter> writer(writer::Factory::create(std::cout));
+		SchemeHandler handler(writer.get(), configuration);
+		reader->addHandler(&handler);
+		reader->process();
+
+		resultCode = CLI::EXIT_CODE_SUCCESS;
+
+	} catch (RelpipeCLIException& e) {
+		fwprintf(stderr, L"Caught CLI exception: %ls\n", e.getMessge().c_str());
+		fwprintf(stderr, L"Debug: Input stream: eof=%ls, lastRead=%d\n", (cin.eof() ? L"true" : L"false"), cin.gcount());
+		resultCode = e.getExitCode();
+	} catch (RelpipeReaderException& e) {
+		fwprintf(stderr, L"Caught Reader exception: %ls\n", e.getMessge().c_str());
+		fwprintf(stderr, L"Debug: Input stream: eof=%ls, lastRead=%d\n", (cin.eof() ? L"true" : L"false"), cin.gcount());
+		resultCode = CLI::EXIT_CODE_DATA_ERROR;
+	} catch (SchemeException& e) {
+		fwprintf(stderr, L"Caught Scheme exception: %ls\n", e.getMessge().c_str());
+		fwprintf(stderr, L"Debug: Input stream: eof=%ls, lastRead=%d\n", (cin.eof() ? L"true" : L"false"), cin.gcount());
+		resultCode = CLI::EXIT_CODE_UNEXPECTED_ERROR;
+	}
+
+	exit(resultCode);
+}
+
+int main(int argc, char**argv) {
+	scm_boot_guile(argc, argv, relpipeMain, nullptr);
+	return 222; // never reached – see exit(resultCode) above
+}