Index: /cpp/frams/genetics/fB/fB_conv.cpp
===================================================================
--- /cpp/frams/genetics/fB/fB_conv.cpp	(revision 796)
+++ /cpp/frams/genetics/fB/fB_conv.cpp	(revision 797)
@@ -1,2 +1,6 @@
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 1999-2018  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
 #include "fB_conv.h"
 
@@ -89,5 +93,14 @@
 			continue;
 		}
-		fH_Handle *handle = convertCharacterToHandle(gene[2], dims, start, end, ranges);
+		int hclasspos = 2;
+		if (gene[2] == '"')
+		{
+			hclasspos--;
+			if (!getNextCharId(gene, hclasspos))
+			{
+				return "";
+			}
+		}
+		fH_Handle *handle = convertCharacterToHandle(gene[hclasspos], dims, start, end, ranges);
 		ParamEntry *tab = creature.getParamTab(handle->type);
 		void *obj = ParamObject::makeObject(tab);
@@ -97,13 +110,38 @@
 
 		int propindex = 0;
-		int z = 3;
+		int z = hclasspos;
+		if (gene[z] == '"')
+		{
+			z--;
+			if (!getNextCharId(gene, z))
+			{
+				delete handle;
+				ParamObject::freeObject(obj);
+				return "";
+			}
+		}
 		endoffset = 0;
 		if (gene.indexOf("zz", 0) != -1) endoffset = 2;
+		int nclassdefcount = 1;
 		while (z < gene.len() - endoffset)
 		{
-			if (processNextLetter(creature, handle, par, gene, propindex, z, ranges) == -1)
-			{
-				logMessage("GenoConv_fBH", "convert", LOG_WARN, "Property of fH could not be parsed");
-			}
+			if (processNextLetter(creature, handle, par, gene, propindex, z, ranges, nclassdefcount) == -1)
+			{
+				logMessage("GenoConv_fBH", "convert", LOG_ERROR, "Property of fH could not be parsed");
+				delete handle;
+				ParamObject::freeObject(obj);
+				return "";
+			}
+		}
+		if (handle->type == fHBodyType::NEURON && propindex < par.getPropCount())
+		{
+			SString nclass;
+			if (!getNeuroClass(gene, nclass, nclassdefcount))
+			{
+				delete handle;
+				ParamObject::freeObject(obj);
+				return "";
+			}
+			par.setStringById(FH_PE_NEURO_DET, nclass);
 		}
 		handle->loadProperties(par);
@@ -132,5 +170,53 @@
 }
 
-int GenoConv_fBH::processNextLetter(fH_Builder &creature, fH_Handle *&currhandle, Param &par, SString gene, int &propindex, int &i, std::vector<IRange> ranges[3])
+bool GenoConv_fBH::getNextCharId(SString genotype, int &i)
+{
+	i++;
+	if (genotype[i] == '"')
+	{
+		int nextid = i + 1;
+		do
+		{
+			nextid = genotype.indexOf('"', nextid);
+			if (nextid == -1)
+			{
+				return false;
+			}
+			nextid++;
+		}
+		while (genotype[nextid] == '"');
+		i = nextid;
+	}
+	return true;
+}
+
+bool GenoConv_fBH::getNeuroClass(SString gene, SString &def, int nclassdefcount)
+{
+	SString lastdef = "N";
+	int nclass = 0;
+	int pos = 0;
+	while (nclass < nclassdefcount)
+	{
+		pos = gene.indexOf('\"', pos);
+		if (pos == -1)
+		{
+			def = lastdef;
+			return true;
+		}
+		pos++;
+		SString currdef;
+		if (gene.indexOf('\"', pos) == -1 || !gene.getNextToken(pos, currdef, '\"'))
+		{
+			def = lastdef;
+			return false;
+		}
+		lastdef = currdef;
+		nclass++;
+	}
+	def = lastdef;
+	return true;
+}
+
+int GenoConv_fBH::processNextLetter(fH_Builder &creature, fH_Handle *&currhandle, Param &par, SString gene, int &propindex, int &i, std::vector<IRange> ranges[3], int &nclassdefcount)
 {
 	if (propindex >= par.getPropCount())
@@ -147,5 +233,6 @@
 		par.setDefault();
 		propindex = 0;
-		i++;
+		if (!getNextCharId(gene, i))
+			return -1;
 		return 0;
 	}
@@ -166,12 +253,19 @@
 			}
 			propindex++;
-			i++;
+			if (!getNextCharId(gene, i))
+				return -1;
 			return 0;
 		}
 		else if (currhandle->type == fHBodyType::NEURON && *par.id(propindex) == 'd')
 		{
-			//TODO: handle neuron classes and properties
+			//When 'd' property appears for i-th element in gene, the method
+			//looks for i-th neuron definition
+			SString nclass;
+			if (!getNeuroClass(gene, nclass, nclassdefcount)) return -1;
+			par.setString(propindex, nclass);
 			propindex++;
-			i++;
+			nclassdefcount++;
+			if (!getNextCharId(gene, i))
+				return -1;
 			return 0;
 		}
Index: /cpp/frams/genetics/fB/fB_conv.h
===================================================================
--- /cpp/frams/genetics/fB/fB_conv.h	(revision 796)
+++ /cpp/frams/genetics/fB/fB_conv.h	(revision 797)
@@ -1,2 +1,6 @@
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 1999-2018  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
 #ifndef _FB_CONV_H_
 #define _FB_CONV_H_
@@ -8,8 +12,10 @@
 {
 private:
+	bool getNextCharId(SString genotype, int &i);
 	double convertCharacterTo01(char c);
 	double convertCharacterToWeight(char c);
 	static fH_Handle* convertCharacterToHandle(char c, int dims, int start, int end, std::vector<IRange> ranges[3]);
-	int processNextLetter(fH_Builder &creature, fH_Handle *&currhandle, Param &par, SString gene, int &propindex, int &i, std::vector<IRange> ranges[3]);
+	int processNextLetter(fH_Builder &creature, fH_Handle *&currhandle, Param &par, SString gene, int &propindex, int &i, std::vector<IRange> ranges[3], int &nclassdefcount);
+	bool getNeuroClass(SString gene, SString &def, int nclassdefcount);
 
 public:
Index: /cpp/frams/genetics/fB/fB_general.h
===================================================================
--- /cpp/frams/genetics/fB/fB_general.h	(revision 796)
+++ /cpp/frams/genetics/fB/fB_general.h	(revision 797)
@@ -1,2 +1,6 @@
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 1999-2018  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
 #ifndef _FB_GENERAL_H_
 #define _FB_GENERAL_H_
@@ -10,8 +14,13 @@
 	{
 		int start = 0;
+		int prev = 0;
 		int count = -1;
 		do {
 			count++;
 			start = geno.indexOf("aa", start) + 1; // +1 is for progress, starting codons can overlap
+			int quotecount = 0;
+			for (int q = prev; q < start; q++) if (geno[q] == '\"') quotecount++;
+			prev = start;
+			if (quotecount % 2 != 0) count--; // 'aa' sequence is within quotes
 		} while (start - 1 != -1);
 		return count;
@@ -25,4 +34,7 @@
 			count++;
 			start = genotype.indexOf("aa", start) + 1;
+			int quotecount = 0;
+			for (int q = 0; q < start; q++) if (genotype[q] == '\"') quotecount++;
+			if (quotecount % 2 != 0) count--; // 'aa' sequence is within quotes
 		} while (start - 1 != -1 && count != i);
 		if (start - 1 == -1) // there is no gene with a given "i"
@@ -33,5 +45,15 @@
 		}
 		end = start;
-		end = genotype.indexOf("zz", end);
+		int quotecount = 0;
+		do {
+			quotecount = 0;
+			end = genotype.indexOf("zz", end);
+			if (end != -1)
+			{
+				for (int q = start; q < end; q++) if (genotype[q] == '\"') quotecount++;
+				if (quotecount % 2 != 0) end++;
+			}
+		} while (quotecount % 2 != 0 && end != -1);
+
 		if (end == -1) end = genotype.len();
 		else end += 2;
Index: /cpp/frams/genetics/fB/fB_oper.cpp
===================================================================
--- /cpp/frams/genetics/fB/fB_oper.cpp	(revision 796)
+++ /cpp/frams/genetics/fB/fB_oper.cpp	(revision 797)
@@ -1,2 +1,6 @@
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 1999-2018  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
 #include <frams/util/sstring.h>
 #include <vector>
@@ -5,4 +9,5 @@
 #include "fB_general.h"
 #include "fB_oper.h"
+#include "../fH/fH_oper.h"
 
 #define FIELDSTRUCT Geno_fB
@@ -10,9 +15,10 @@
 static ParamEntry GENOfBparam_tab[] =
 {
-	{ "Genetics: fB", 3, FB_MUT_COUNT + FB_XOVER_COUNT, }, // ask about it
+	{ "Genetics: fB", 3, FB_MUT_COUNT + FB_XOVER_COUNT, },
 	{ "Genetics: fB: Mutation", },
 	{ "Genetics: fB: Crossover", },
 	{ "fB_mut_substitution", 1, 0, "Substitution", "f 0 1 0.6", FIELD(mutationprobs[FB_SUBSTITUTION]), "Probability of mutation by changing single random letter in genotype", },
-	{ "fB_mut_insertion", 1, 0, "Insertion", "f 0 1 0.1", FIELD(mutationprobs[FB_INSERTION]), "Probability of mutation by inserting characters in random place of genotype", },
+	{ "fB_mut_insertion", 1, 0, "Insertion", "f 0 1 0.95", FIELD(mutationprobs[FB_INSERTION]), "Probability of mutation by inserting characters in random place of genotype", },
+	{ "fB_mut_nclassins", 1, 0, "Insertion of neuron class definition", "f 0 1 0.05", FIELD(mutationprobs[FB_NCLASSINS]), "Probability of mutation by inserting neuron class definition in random place of genotype", },
 	{ "fB_mut_deletion", 1, 0, "Deletion", "f 0 1 0.1", FIELD(mutationprobs[FB_DELETION]), "Probability of mutation by deleting random characters in genotype", },
 	{ "fB_mut_duplication", 1, 0, "Duplication", "f 0 1 0.05", FIELD(mutationprobs[FB_DUPLICATION]), "Probability of mutation by copying single *gene* of genotype and appending it to the beginning of this genotype", },
@@ -81,5 +87,27 @@
 		if (!islower(genotype[i]))
 		{
-			return i + 1;
+			if (genotype[i] == '"')
+			{
+				SString neuclassdef;
+				int nextid = i + 1;
+				if (!genotype.getNextToken(nextid, neuclassdef, '"'))
+				{
+					return i + 1;
+				}
+				Neuro *neu = new Neuro();
+				neu->setDetails(neuclassdef);
+
+				bool isclass = neu->getClass() ? true : false;
+				delete neu;
+				if (!isclass)
+				{
+					return i + 1;
+				}
+				i = nextid;
+			}
+			else
+			{
+				return i + 1;
+			}
 		}
 	}
@@ -121,8 +149,30 @@
 		if (!isalpha(genotype[i]))
 		{
-			return GENOPER_OPFAIL;
+			if (genotype[i] == '"')
+			{
+				SString neuclassdef;
+				int nextid = i + 1;
+				if (!genotype.getNextToken(nextid, neuclassdef, '"'))
+				{
+					return i + 1;
+				}
+				Neuro *neu = new Neuro();
+				neu->setDetails(neuclassdef);
+
+				bool isclass = neu->getClass() ? true : false;
+				delete neu;
+				if (!isclass)
+				{
+					return i + 1;
+				}
+				i = nextid;
+			}
+			else
+			{
+				return GENOPER_OPFAIL;
+			}
 		}
 		// if character is uppercase, then convert it to lowercase
-		if (isupper(genotype[i]))
+		else if (isupper(genotype[i]))
 		{
 			genotype.directWrite()[i] = tolower(genotype[i]);
@@ -142,4 +192,42 @@
 	}
 	return GENOPER_OK;
+}
+
+SString Geno_fB::detokenizeSequence(std::list<SString> tokenlist)
+{
+	SString res = "";
+	for (std::list<SString>::iterator it = tokenlist.begin(); it != tokenlist.end(); it++)
+	{
+		res += (*it);
+	}
+	return res;
+}
+
+std::list<SString> Geno_fB::tokenizeSequence(SString genotype)
+{
+	std::list<SString> res;
+	int i = 0;
+	while (i < genotype.len())
+	{
+		// if character is not alphabetic - error
+		if (isalpha(genotype[i]))
+		{
+			SString el = "";
+			el += genotype[i];
+			res.push_back(el);
+			i++;
+		}
+		else
+		{
+			SString neuclassdef;
+			i++;
+			genotype.getNextToken(i, neuclassdef, '"');
+			SString ndef = "\"";
+			ndef += neuclassdef;
+			ndef += "\"";
+			res.push_back(ndef);
+		}
+	}
+	return res;
 }
 
@@ -157,43 +245,79 @@
 	case FB_SUBSTITUTION:
 	{
-		int rndid = randomN(line.len()); // select random letter from genotype
+		std::list<SString> tokenized = tokenizeSequence(line);
+		int rndid = randomN(tokenized.size()); // select random letter from genotype
 		// increment/decrement character - when overflow happens, this method
 		// uses reflect method
-		if (randomN(2) == 0)
-		{
-			if (line[rndid] == 'a') line.directWrite()[rndid] = 'b';
-			else line.directWrite()[rndid] = line[rndid] - 1;
+		std::list<SString>::iterator it = tokenized.begin();
+		std::advance(it, rndid);
+		SString t = (*it);
+		if ((*it).len() == 1)
+		{
+			if (randomN(2) == 0)
+			{
+				if ((*it)[0] == 'a') (*it).directWrite()[0] = 'b';
+				else (*it).directWrite()[0] = (*it)[0] - 1;
+			}
+			else
+			{
+				if ((*it)[0] == 'z') (*it).directWrite()[0] = 'y';
+				else (*it).directWrite()[0] = (*it)[0] + 1;
+			}
+			chg = 1.0 / line.len();
 		}
 		else
 		{
-			if (line[rndid] == 'z') line.directWrite()[rndid] = 'y';
-			else line.directWrite()[rndid] = line[rndid] + 1;
-		}
+			// first method needs to extract quotes
+			SString def = (*it);
+			def = def.substr(1, def.len() - 2);
+			Geno_fH::mutateNeuronProperties(def);
+			SString res = "\"";
+			res += def;
+			res += "\"";
+			(*it) = res;
+			chg = (double)def.len() / line.len();
+		}
+		line = detokenizeSequence(tokenized);
+		break;
+	}
+	case FB_INSERTION:
+	{
 		chg = 1.0 / line.len();
-		break;
-	}
-	case FB_INSERTION:
+		std::list<SString> tokenized = tokenizeSequence(line);
+		int rndid = randomN(tokenized.size()); // select random insertion point
+		std::list<SString>::iterator it = tokenized.begin();
+		std::advance(it, rndid);
+		SString letter = "a";
+		letter.directWrite()[0] = 'a' + randomN(26);
+		tokenized.insert(it, letter);
+		line = detokenizeSequence(tokenized);
+		break;
+	}
+	case FB_NCLASSINS:
+	{
+		std::list<SString> tokenized = tokenizeSequence(line);
+		std::list<SString>::iterator it = tokenized.begin();
+		int rndid = randomN(tokenized.size()); // select random insertion point
+		std::advance(it, rndid);
+		NeuroClass *cls = getRandomNeuroClass();
+		SString classdef = cls->getName();
+		Geno_fH::mutateNeuronProperties(classdef);
+		SString res = "\"";
+		res += classdef;
+		res += "\"";
+		tokenized.insert(it, res);
+		chg = (double)classdef.len() / line.len();
+		line = detokenizeSequence(tokenized);
+		break;
+	}
+	case FB_DELETION:
 	{
 		chg = 1.0 / line.len();
-		int rndid = randomN(genotype.len()); // select random insertion point
-		char letter = 'a' + randomN(26);
-		SString result = line.substr(0, rndid);
-		result += letter;
-		result += line.substr(rndid);
-		line = result;
-		break;
-	}
-	case FB_DELETION:
-	{
-		chg = 1.0 / line.len();
-		int rndid = randomN(line.len()); // select random insertion point
-		if (rndid == line.len() - 1)
-		{
-			line = line.substr(0, line.len() - 1);
-		}
-		else
-		{
-			line = line.substr(0, rndid) + line.substr(rndid + 1);
-		}
+		std::list<SString> tokenized = tokenizeSequence(line);
+		std::list<SString>::iterator it = tokenized.begin();
+		int rndid = randomN(tokenized.size()); // select random deletion point
+		std::advance(it, rndid);
+		tokenized.erase(it);
+		line = detokenizeSequence(tokenized);
 		break;
 	}
@@ -210,15 +334,30 @@
 	case FB_TRANSLOCATION:
 	{
-		std::vector<int> cuts(4);
+		std::list<SString> tokenized = tokenizeSequence(line);
+		std::vector<unsigned int> cuts(4);
 		for (int i = 0; i < 4; i++)
 		{
-			cuts[i] = randomN(line.len());
+			cuts[i] = randomN(tokenized.size());
 		}
 		std::sort(cuts.begin(), cuts.end());
-		SString first = line.substr(cuts[0], cuts[1] - cuts[0]);
-		SString second = line.substr(cuts[2], cuts[3] - cuts[2]);
-		SString result = line.substr(0, cuts[0]) + second +
-			line.substr(cuts[1], cuts[2] - cuts[1]) + first + line.substr(cuts[3]);
-		line = result;
+		std::vector<std::list<SString>::iterator> iters(4);
+		for (int i = 0; i < 4; i++)
+		{
+			iters[i] = tokenized.begin();
+			std::advance(iters[i], cuts[i]);
+		}
+
+		std::list<SString> res;
+		res.insert(res.end(), tokenized.begin(), iters[0]);
+		res.insert(res.end(), iters[2], iters[3]);
+		res.insert(res.end(), iters[1], iters[2]);
+		res.insert(res.end(), iters[0], iters[1]);
+		res.insert(res.end(), iters[3], tokenized.end());
+
+//		SString first = line.substr(cuts[0], cuts[1] - cuts[0]);
+//		SString second = line.substr(cuts[2], cuts[3] - cuts[2]);
+//		SString result = line.substr(0, cuts[0]) + second +
+//			line.substr(cuts[1], cuts[2] - cuts[1]) + first + line.substr(cuts[3]);
+		line = detokenizeSequence(res);
 		chg = (float)(cuts[3] - cuts[2] + cuts[1] - cuts[0]) / line.len();
 		break;
Index: /cpp/frams/genetics/fB/fB_oper.h
===================================================================
--- /cpp/frams/genetics/fB/fB_oper.h	(revision 796)
+++ /cpp/frams/genetics/fB/fB_oper.h	(revision 797)
@@ -1,6 +1,11 @@
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 1999-2018  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
 #ifndef _FB_OPER_H_
 #define _FB_OPER_H_
 
 #include "../genooperators.h"
+#include <list>
 
 /** @name Codes for general fB mutation types */
@@ -8,8 +13,9 @@
 #define FB_SUBSTITUTION  0 ///<Relative probability of mutation by changing single random letter in genotype (substitution)
 #define FB_INSERTION     1 ///<Relative probability of mutation by inserting characters in random place of genotype
-#define FB_DELETION      2 ///<Relative probability of mutation by deleting random characters in genotype
-#define FB_DUPLICATION   3 ///<Relative probability of mutation by copying single *gene* of genotype and appending it to the beginning of this genotype
-#define FB_TRANSLOCATION 4 ///<Relative probability of mutation by replacing two substrings in genotype
-#define FB_MUT_COUNT     5 ///<Count of mutation types
+#define FB_NCLASSINS     2 ///<Relative probability of mutation by inserting neuron class definition in random place of genotype
+#define FB_DELETION      3 ///<Relative probability of mutation by deleting random characters in genotype
+#define FB_DUPLICATION   4 ///<Relative probability of mutation by copying single *gene* of genotype and appending it to the beginning of this genotype
+#define FB_TRANSLOCATION 5 ///<Relative probability of mutation by replacing two substrings in genotype
+#define FB_MUT_COUNT     6 ///<Count of mutation types
 //@}
 
@@ -25,4 +31,6 @@
 private:
 	bool hasStick(SString genotype);
+	SString detokenizeSequence(std::list<SString> tokenlist);
+	std::list<SString> tokenizeSequence(SString genotype);
 
 public:
Index: /cpp/frams/genetics/fH/fH_conv.cpp
===================================================================
--- /cpp/frams/genetics/fH/fH_conv.cpp	(revision 796)
+++ /cpp/frams/genetics/fH/fH_conv.cpp	(revision 797)
@@ -1,2 +1,6 @@
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 1999-2018  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
 #include "fH_conv.h"
 
Index: /cpp/frams/genetics/fH/fH_conv.h
===================================================================
--- /cpp/frams/genetics/fH/fH_conv.h	(revision 796)
+++ /cpp/frams/genetics/fH/fH_conv.h	(revision 797)
@@ -1,2 +1,6 @@
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 1999-2018  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
 #ifndef _FH_CONV_H_
 #define _FH_CONV_H_
Index: /cpp/frams/genetics/fH/fH_general.cpp
===================================================================
--- /cpp/frams/genetics/fH/fH_general.cpp	(revision 796)
+++ /cpp/frams/genetics/fH/fH_general.cpp	(revision 797)
@@ -1,2 +1,6 @@
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 1999-2018  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
 #include <string>
 #include <limits>
@@ -13,7 +17,7 @@
 // Methods for loading handles
 
-const char *fH_part_names[PART_PROPS_COUNT] = { "dn", "fr", "ing", "as" };
-
-const char *fH_joint_names[JOINT_PROPS_COUNT] = { "stif", "rotstif", "stam" };
+const char *fH_part_names[FH_PART_PROPS_COUNT] = { "dn", "fr", "ing", "as" };
+
+const char *fH_joint_names[FH_JOINT_PROPS_COUNT] = { "stif", "rotstif", "stam" };
 
 void fH_Handle::loadProperties(Param par)
@@ -106,5 +110,5 @@
 
 	Part p;
-	for (int i = 0; i < PART_PROPS_COUNT; i++)
+	for (int i = 0; i < FH_PART_PROPS_COUNT; i++)
 	{
 		stickmut.addProperty(&p.properties().getParamTab()[p.properties().findId(fH_part_names[i]) + p.properties().getGroupCount()], -1);
@@ -112,5 +116,5 @@
 
 	Joint j;
-	for (int i = 0; i < JOINT_PROPS_COUNT; i++)
+	for (int i = 0; i < FH_JOINT_PROPS_COUNT; i++)
 	{
 		stickmut.addProperty(&j.properties().getParamTab()[j.properties().findId(fH_joint_names[i]) + j.properties().getGroupCount()], -1);
@@ -119,8 +123,8 @@
 
 	Neuro n;
-	neuronmut.addProperty(&n.properties().getParamTab()[n.properties().findId(PE_NEURO_DET) + n.properties().getGroupCount()], -1);
+	neuronmut.addProperty(&n.properties().getParamTab()[n.properties().findId(FH_PE_NEURO_DET) + n.properties().getGroupCount()], -1);
 
 	Param tmp(f0_neuroconn_paramtab, NULL);
-	connectionmut.addProperty(&tmp.getParamTab()[tmp.findId(PE_CONN_WEIGHT) + tmp.getGroupCount()], -1);
+	connectionmut.addProperty(&tmp.getParamTab()[tmp.findId(FH_PE_CONN_WEIGHT) + tmp.getGroupCount()], -1);
 
 	stickparamtab = ParamObject::makeParamTab((ParamInterface *)&stickmut, 0, 0, stickmut.firstMutableIndex());
@@ -343,6 +347,6 @@
 {
 	Param par(tab, obj);
-	double partprops[PART_PROPS_COUNT];
-	for (int i = 0; i < PART_PROPS_COUNT; i++)
+	double partprops[FH_PART_PROPS_COUNT];
+	for (int i = 0; i < FH_PART_PROPS_COUNT; i++)
 	{
 		partprops[i] = par.getDouble(2 * getDimensions() + i);
@@ -357,5 +361,5 @@
 	{
 		par.select(child->obj);
-		for (int i = 0; i < PART_PROPS_COUNT; i++)
+		for (int i = 0; i < FH_PART_PROPS_COUNT; i++)
 		{
 			partprops[i] += par.getDouble(2 * getDimensions() + i);
@@ -364,5 +368,5 @@
 	}
 
-	for (int i = 0; i < PART_PROPS_COUNT; i++)
+	for (int i = 0; i < FH_PART_PROPS_COUNT; i++)
 	{
 		partprops[i] /= stickscount;
@@ -522,4 +526,5 @@
 			msg += det + "\" does not exist";
 			logMessage("fH_Builder", "developBrain", LOG_ERROR, msg.c_str());
+			delete currneu->neuron;
 			return -1;
 		}
@@ -601,5 +606,7 @@
 		if (!beginneu)
 		{
-			logMessage("fH_Builder", "developBrain", LOG_WARN, "There are no available neurons with outputs, connection could not be established");
+			// due to often appearance of connection genes in fB encoding, this
+			// log message is commented
+			// logMessage("fH_Builder", "developBrain", LOG_DEBUG, "There are no available neurons with outputs, connection could not be established");
 			continue;
 		}
@@ -628,5 +635,7 @@
 		if (!endneu)
 		{
-			logMessage("fH_Builder", "developBrain", LOG_WARN, "There are no available neurons with free inputs, connection could not be established");
+			// due to often appearance of connection genes in fB encoding, this
+			// log message is commented
+			// logMessage("fH_Builder", "developBrain", LOG_DEBUG, "There are no available neurons with free inputs, connection could not be established");
 			continue;
 		}
Index: /cpp/frams/genetics/fH/fH_general.h
===================================================================
--- /cpp/frams/genetics/fH/fH_general.h	(revision 796)
+++ /cpp/frams/genetics/fH/fH_general.h	(revision 797)
@@ -1,2 +1,6 @@
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 1999-2018  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
 #ifndef _FH_GENERAL_H_
 #define _FH_GENERAL_H_
@@ -14,8 +18,8 @@
 #define HANDLE_VECTOR_TYPE "f -1.0 1.0 0.0" ///<Vector values type definition
 #define STICKH_LENGTH_TYPE "f 0.001 1.999 1.0" ///<Length of stick handle. Minimum should not be equal to 0, because calculating direction of next part from current stick with length 0 would be impossible
-#define PART_PROPS_COUNT   4 ///<Count of part properties
-#define JOINT_PROPS_COUNT  3 ///<Count of joint properties
-#define PE_NEURO_DET       "d" ///<Id of details type definition in f0_neuro_paramtab
-#define PE_CONN_WEIGHT     "w" ///<Id of weight type definition in f0_neuroconn_paramtab
+#define FH_PART_PROPS_COUNT   4 ///<Count of part properties
+#define FH_JOINT_PROPS_COUNT  3 ///<Count of joint properties
+#define FH_PE_NEURO_DET       "d" ///<Id of details type definition in f0_neuro_paramtab
+#define FH_PE_CONN_WEIGHT     "w" ///<Id of weight type definition in f0_neuroconn_paramtab
 //@}
 
Index: /cpp/frams/genetics/fH/fH_oper.cpp
===================================================================
--- /cpp/frams/genetics/fH/fH_oper.cpp	(revision 796)
+++ /cpp/frams/genetics/fH/fH_oper.cpp	(revision 797)
@@ -1,2 +1,6 @@
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 1999-2018  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
 #include "fH_oper.h"
 
@@ -421,20 +425,9 @@
 }
 
-void Geno_fH::mutateNeuronHandleProperties(fH_NeuronHandle *handle, ParamEntry *tab, bool userandomclass)
+void Geno_fH::mutateNeuronProperties(SString &det)
 {
 	Neuro neu;
-	Param hpar(tab, handle->obj);
-	SString det = hpar.getStringById("d");
+	det = det == "" ? "N" : det;
 	neu.setDetails(det == "" ? "N" : det);
-	NeuroClass *nc = neu.getClass();
-
-	if (userandomclass)
-	{
-		nc = getRandomNeuroClass();
-		if (!nc) nc = Neuro::getClass("N");
-	}
-
-	det = nc->getName();
-	neu.setDetails(det);
 
 	SyntParam par = neu.classProperties();
@@ -455,32 +448,84 @@
 		if (props != "")
 		{
+			det = neu.getClass()->name;
 			det += ": ";
 			det += props;
-			hpar.setStringById("d", det);
-		}
-	}
-}
-
-uint32_t Geno_fH::style(const char *geno, int pos)
-{
-	char ch = geno[pos];
-	uint32_t style = GENSTYLE_CS(0, GENSTYLE_STRIKEOUT);
-	if (pos == 0 || geno[pos - 1] == '\n' || ch == ':') // single-character handle type and all colons
-	{
-		style = GENSTYLE_CS(GENCOLOR_TEXT, GENSTYLE_BOLD);
-	}
-	else if (isalpha(ch)) // properties name
-	{
-		style = GENSTYLE_RGBS(0, 200, 0, GENSTYLE_BOLD);
-	}
-	else if (isdigit(ch) || strchr(",.=", ch)) // properties values
-	{
-		style = GENSTYLE_CS(GENCOLOR_TEXT, GENSTYLE_NONE);
-	}
-	else if (ch == '\"')
-	{
-		style = GENSTYLE_RGBS(200, 0, 0, GENSTYLE_BOLD);
-	}
-
-	return style;
-}
+		}
+	}
+}
+
+void Geno_fH::mutateNeuronHandleProperties(fH_NeuronHandle *handle, ParamEntry *tab, bool userandomclass)
+{
+	Neuro neu;
+	Param hpar(tab, handle->obj);
+	SString det = hpar.getStringById("d");
+	neu.setDetails(det == "" ? "N" : det);
+	NeuroClass *nc = neu.getClass();
+
+	if (userandomclass)
+	{
+		nc = getRandomNeuroClass();
+		if (!nc) nc = Neuro::getClass("N");
+	}
+
+	det = nc->getName();
+
+	mutateNeuronProperties(det);
+
+	hpar.setStringById("d", det);
+}
+
+//uint32_t Geno_fH::style(const char *geno, int pos)
+//{
+//	char ch = geno[pos];
+//	uint32_t style = GENSTYLE_CS(0, GENSTYLE_STRIKEOUT);
+//	if (pos == 0 || geno[pos - 1] == '\n' || ch == ':') // single-character handle type and all colons
+//	{
+//		style = GENSTYLE_CS(GENCOLOR_TEXT, GENSTYLE_BOLD);
+//	}
+//	else if (isalpha(ch)) // properties name
+//	{
+//		style = GENSTYLE_RGBS(0, 200, 0, GENSTYLE_BOLD);
+//	}
+//	else if (isdigit(ch) || strchr(",.=", ch)) // properties values
+//	{
+//		style = GENSTYLE_CS(GENCOLOR_TEXT, GENSTYLE_NONE);
+//	}
+//	else if (ch == '\"')
+//	{
+//		style = GENSTYLE_RGBS(200, 0, 0, GENSTYLE_BOLD);
+//	}
+//
+//	return style;
+//}
+
+uint32_t Geno_fH::style(const char *g, int pos)
+{
+   char ch=g[pos];
+   uint32_t style=GENSTYLE_CS(0,GENSTYLE_NONE); //default, should be changed below
+
+   int pp=pos; //detect comment line
+   while (pp>1 && g[pp-1]!='\n') pp--;
+   if (g[pp]=='#') return GENSTYLE_RGBS(0,220,0,GENSTYLE_NONE); //comment line
+
+   if (pos==0 || g[pos-1]=='\n' || ch==':' || ch==',') style=GENSTYLE_CS(0,GENSTYLE_BOLD); else
+     if (ch=='\"') style=GENSTYLE_RGBS(150,0,0,GENSTYLE_BOLD); else
+     {
+      int cudz=0,neuclass=1; //ile cudz. do poczatku linii; czy w nazwie neuroklasy?
+      while (pos>0)
+      {
+         pos--;
+         if (g[pos]=='\"') cudz++;
+         if (cudz==0 && (g[pos]==':' || g[pos]==',')) neuclass=0;
+         if (g[pos]=='\n') break;
+      }
+      if (cudz%2)
+      {
+         if (neuclass) style=GENSTYLE_RGBS(150,0,150,GENSTYLE_BOLD); else //neuroclass
+           if (isalpha(ch)) style=GENSTYLE_RGBS(255,140,0,GENSTYLE_BOLD); else //property
+             style=GENSTYLE_RGBS(200,0,0,GENSTYLE_NONE);
+      } else
+        if (isalpha(ch)) style=GENSTYLE_RGBS(0,0,200,GENSTYLE_BOLD);
+     }
+   return style;
+}
Index: /cpp/frams/genetics/fH/fH_oper.h
===================================================================
--- /cpp/frams/genetics/fH/fH_oper.h	(revision 796)
+++ /cpp/frams/genetics/fH/fH_oper.h	(revision 797)
@@ -1,2 +1,6 @@
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 1999-2018  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
 #ifndef _FH_OPER_H_
 #define _FH_OPER_H_
@@ -87,4 +91,11 @@
 	double addoperations[FH_ADD_OPCOUNT]; ///<relative probabilities of selecting mutation addition subtypes
 
+	// TODO add to GenoOperators?
+	/**
+	 * Mutates properties of neuron and returns full neuron class description.
+	 * @param det current neuron class definition with its parameters
+	 */
+	static void mutateNeuronProperties(SString &det);
+
 	Geno_fH();
 
Index: /cpp/frams/genetics/fL/fL_conv.cpp
===================================================================
--- /cpp/frams/genetics/fL/fL_conv.cpp	(revision 796)
+++ /cpp/frams/genetics/fL/fL_conv.cpp	(revision 797)
@@ -1,6 +1,20 @@
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 1999-2018  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
 #include "fL_conv.h"
+#include "fL_general.h"
 
 SString GenoConv_fL0::convert(SString &i, MultiMap *map, bool using_checkpoints)
 {
-	return "";
+	fL_Builder builder(NULL != map, using_checkpoints);
+	if (builder.parseGenotype(i) != 0)  return "";
+	double neededtime;
+	Model *m = builder.developModel(neededtime);
+	if (!m) return SString();
+	if (NULL != map)
+		m->getCurrentToF0Map(*map);
+	SString gen = m->getF0Geno().getGenes();
+	delete m;
+	return gen;
 }
Index: /cpp/frams/genetics/fL/fL_conv.h
===================================================================
--- /cpp/frams/genetics/fL/fL_conv.h	(revision 796)
+++ /cpp/frams/genetics/fL/fL_conv.h	(revision 797)
@@ -1,2 +1,6 @@
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 1999-2018  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
 #ifndef _FL_CONV_
 #define _FL_CONV_
Index: /cpp/frams/genetics/fL/fL_general.cpp
===================================================================
--- /cpp/frams/genetics/fL/fL_general.cpp	(revision 796)
+++ /cpp/frams/genetics/fL/fL_general.cpp	(revision 797)
@@ -1,11 +1,17 @@
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 1999-2018  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
 #include <algorithm>
 #include <stack>
 #include "fL_general.h"
-
-const char *fL_part_names[PART_PROPS_COUNT] = { "dn", "fr", "ing", "as" };
-const char *fL_part_fullnames[PART_PROPS_COUNT] = { "details", "friction", "ingestion", "assimilation" };
-
-const char *fL_joint_names[JOINT_PROPS_COUNT] = { "stif", "rotstif", "stam" };
-const char *fL_joint_fullnames[JOINT_PROPS_COUNT] = { "stiffness", "rotation stiffness", "stamina" };
+#include <frams/util/multirange.h>
+#include <iterator>
+
+const char *fL_part_names[FL_PART_PROPS_COUNT] = { "dn", "fr", "ing", "as" };
+const char *fL_part_fullnames[FL_PART_PROPS_COUNT] = { "details", "friction", "ingestion", "assimilation" };
+
+const char *fL_joint_names[FL_JOINT_PROPS_COUNT] = { "stif", "rotstif", "stam" };
+const char *fL_joint_fullnames[FL_JOINT_PROPS_COUNT] = { "stiffness", "rotation stiffness", "stamina" };
 
 #define FIELDSTRUCT fL_Word
@@ -14,5 +20,5 @@
 	{ "Word", 1, 2, "w" },
 	{ "name", 0, PARAM_CANOMITNAME, "word name", "s", FIELD(name), },
-	{ "npar", 0, PARAM_CANOMITNAME, "number of parameters", "d 0 3 0", FIELD(npar), },
+	{ "npar", 0, PARAM_CANOMITNAME, "number of parameters", "d 0 " FL_MAXPARAMS " 0", FIELD(npar), },
 	{ 0, 0, 0, }
 };
@@ -23,7 +29,7 @@
 {
 	{ "Rule", 1, 3, "r" },
-	{ "pred", 0, PARAM_CANOMITNAME, "predecessor", "s", FIELD(predecessor), },
-	{ "cond", 0, PARAM_CANOMITNAME, "parameter condition", "s", FIELD(condition), },
-	{ "succ", 0, PARAM_CANOMITNAME, "successor", "s", FIELD(successor), },
+	{ "pred", 0, 0, "predecessor", "s", FIELD(predecessor), },
+	{ "cond", 0, 0, "parameter condition", "s", FIELD(condition), },
+	{ "succ", 0, 0, "successor", "s", FIELD(successor), },
 	{ 0, 0, 0, }
 };
@@ -33,9 +39,10 @@
 ParamEntry fL_builder_paramtab[] =
 {
-	{ "LSystemInfo", 1, 3, "i" },
-	{ "axiom", 0, PARAM_CANOMITNAME, "starting sequence of L-System", "s", FIELD(axiom), },
-	{ "time", 0, PARAM_CANOMITNAME, "development time", "f 1.0 100.0 1.0", FIELD(time), },
-	{ "numckp", 0, PARAM_CANOMITNAME, "number of checkpoints", "d 1 50 1", FIELD(numckp), },
-	{ 0, 0, 0, }
+		{"LSystemInfo", 1, 4, "i"},
+		{"axiom", 0, 0, "starting sequence of L-System", "s", FIELD(axiom),},
+		{"time", 0, PARAM_CANOMITNAME, "development time", "f 0.0 " FL_MAXITER " 1.0", FIELD(time),},
+		{"numckp", 0, PARAM_CANOMITNAME, "number of checkpoints", "d 1 50 1", FIELD(numckp),},
+		{"maxwords", 0, PARAM_CANOMITNAME, "Maximum number of words within genotype sequence", "d -1 9999 -1", FIELD(maxwords),},
+		{0,0,0,}
 };
 #undef FIELDSTRUCT
@@ -134,5 +141,93 @@
 }
 
-int fL_Builder::tokenize(SString sequence, std::list<fL_Word *> &result, int numparams)
+int fL_Builder::createWord(SString token, fL_Word *&word, int numparams, int begin, int end)
+{
+	SString wordn;
+	int tokpos = 0;
+	// if word name cannot be extracted, then return error
+	if (!token.getNextToken(tokpos, wordn, '('))
+	{
+		return 1;
+	}
+	std::string wordname = fL_Builder::trimSpaces(wordn.c_str());
+	// if word cannot be found in available words, then return error
+	if (words.find(wordname) == words.end())
+	{
+		SString message = "Word '";
+		message += wordname.c_str();
+		message += "' in sequence does not exist";
+		logMessage("fL_Builder", "createWord", LOG_ERROR, message.c_str());
+		return 1;
+	}
+
+	if (word) delete word;
+	// create new word and assign parameters
+	word = new fL_Word(false, begin, end);
+
+	*word = *words[wordname];
+
+	SString temp;
+	temp = token.substr(tokpos);
+	temp = temp.substr(0, temp.len() - 1);
+
+	// if word has parameters
+	if (word->npar > 0)
+	{
+		// create ParamObject that will hold parameter data
+		word->data = ParamObject::makeObject(word->tab);
+		Param par(word->tab);
+		par.select(word->data);
+		par.setDefault();
+		ParamInterface::LoadOptions opts;
+
+		// load parameters from string
+		par.load(ParamInterface::FormatSingleLine, temp, &opts);
+		for (int i = 0; i < par.getPropCount(); i++)
+		{
+			SString t(par.id(i));
+			if (word->builtin && (t == SString("d") || t == SString(FL_PE_CONN_ATTR)))
+			{
+				word->parevals.push_back(NULL);
+			}
+			else
+			{
+				// create MathEvaluation object to check if string contained by
+				// parameter is valid
+				double tmp;
+				MathEvaluation *eval = NULL;
+				SString seq = par.getString(i);
+				// if string is empty, then evaluate this with 0
+				// if sequence could not be evaluated, then return error
+				if (seq.len() > 0)
+				{
+					eval = new MathEvaluation(numparams);
+					if (eval->evaluate(seq.c_str(), tmp) != 0)
+					{
+						SString message = "Word in sequence has invalid parameter:  ";
+						message += temp;
+						logMessage("fL_Builder", "createWord", LOG_ERROR, message.c_str());
+						delete eval;
+						delete word;
+						word = NULL;
+						return 1;
+					}
+				}
+				word->parevals.push_back(eval);
+			}
+		}
+	}
+	else if (word->npar == 0 && temp.len() > 0)
+	{
+		SString message = "Too many parameters for word:  ";
+		message += token;
+		logMessage("fL_Builder", "createWord", LOG_ERROR, message.c_str());
+		delete word;
+		word = NULL;
+		return 1;
+	}
+	return 0;
+}
+
+int fL_Builder::tokenize(SString sequence, std::list<fL_Word *> &result, int numparams, int begin, int end)
 {
 	int pos = 0;
@@ -153,5 +248,5 @@
 		if (token.indexOf("[", 0) != -1)
 		{
-			fL_Branch *word = new fL_Branch(fL_Branch::BranchType::OPEN);
+			fL_Branch *word = new fL_Branch(fL_Branch::BranchType::OPEN, begin, end);
 			result.push_back(word);
 			branchcount++;
@@ -168,13 +263,11 @@
 				return 1;
 			}
-			fL_Branch *word = new fL_Branch(fL_Branch::BranchType::CLOSE);
+			fL_Branch *word = new fL_Branch(fL_Branch::BranchType::CLOSE, begin, end);
 			result.push_back(word);
 			branchcount--;
 			continue;
 		}
-		SString wordn;
-		int tokpos = 0;
-		// if word name cannot be extracted, then return error
-		if (!token.getNextToken(tokpos, wordn, '('))
+		fL_Word *word = NULL;
+		if (createWord(token, word, numparams, begin, end) != 0)
 		{
 			SString message = "Error during parsing words sequence:  ";
@@ -183,58 +276,35 @@
 			return 1;
 		}
-		std::string wordname = fL_Builder::trimSpaces(wordn.c_str());
-		// if word cannot be found in available words, then return error
-		if (words.find(wordname) == words.end())
-		{
-			SString message = "Word '";
-			message += wordname.c_str();
-			message += "' in sequence does not exist";
-			logMessage("fL_Builder", "tokenize", LOG_ERROR, message.c_str());
-			return 1;
-		}
-
-		// create new word and assign parameters
-		fL_Word *word = new fL_Word();
-
-		*word = *words[wordname];
-
-		// if word has parameters
-		if (word->npar > 0)
-		{
-			// create ParamObject that will hold parameter data
-			word->data = ParamObject::makeObject(word->tab);
-			Param par(word->tab);
-			par.select(word->data);
-			par.setDefault();
-			ParamInterface::LoadOptions opts;
-
-			SString temp;
-			temp = token.substr(tokpos);
-			temp = temp.substr(0, temp.len() - 1);
-			// load parameters from string
-			par.load(ParamInterface::FormatSingleLine, temp, &opts);
-			for (int i = 0; i < par.getPropCount(); i++)
-			{
-				// create MathEvaluation object to check if string contained by
-				// parameter is valid
-				double tmp;
-				MathEvaluation *eval = new MathEvaluation(numparams);
-				SString seq = par.getString(i);
-				// if string is empty, then evaluate this with 0
-				if (seq.len() == 0)
-				{
-					eval->evaluate("0", tmp);
-				}
-				// if sequence could not be evaluated, then return error
-				else if (eval->evaluate(seq.c_str(), tmp) != 0)
-				{
-					SString message = "Word in sequence has invalid parameter:  ";
-					message += temp;
+		if (word->name == "C")
+		{
+			Param par(word->tab, word->data);
+			SString attr = par.getStringById(FL_PE_CONN_ATTR);
+			if (attr.indexOf("$t", 0) != -1)
+			{
+				logMessage("fL_Builder", "tokenize", LOG_ERROR, "Attractor definition cannot contain time variable");
+				delete word;
+				return 1;
+
+			}
+			if (attr != "")
+			{
+				fL_Word *attrword = NULL;
+				if (createWord(attr, attrword, numparams, begin, end) != 0)
+				{
+					SString message = "Error during parsing attractor word:  ";
+					message += attr;
 					logMessage("fL_Builder", "tokenize", LOG_ERROR, message.c_str());
-					delete eval;
 					delete word;
+					if (attrword) delete attrword;
 					return 1;
 				}
-				word->parevals.push_back(eval);
+				if (attrword->builtin)
+				{
+					logMessage("fL_Builder", "tokenize", LOG_ERROR, "Attractor words cannot be built-in");
+					delete word;
+					delete attrword;
+					return 1;
+				}
+				delete attrword;
 			}
 		}
@@ -272,4 +342,6 @@
 		parevals = src.parevals;
 
+		builtin = src.builtin;
+
 		data = NULL; // properties cannot be copied
 	}
@@ -303,4 +375,5 @@
 
 	builder->words[this->name.c_str()] = this;
+	builder->wordnames.push_back(this->name.c_str());
 	return 0;
 }
@@ -318,7 +391,18 @@
 	*objpred = *builder->words[predecessor.c_str()];
 
+	if (objpred->builtin)
+	{
+		logMessage("fL_Rule", "processDefinition", LOG_ERROR, "Builtin words cannot be predecessors");
+		return 1;
+	}
+
 	// parse condition
 	if (condition != "")
 	{
+		if (objpred->builtin && (objpred->name == "N" || objpred->name == "C"))
+		{
+			logMessage("fL_Rule", "processDefinition", LOG_ERROR, "Rules with neuron/connection word predecessors cannot contain conditions");
+			return 1;
+		}
 		std::string cond = condition.c_str();
 		condeval = new MathEvaluation(objpred->npar);
@@ -340,5 +424,5 @@
 	}
 
-	if (builder->tokenize(successor, objsucc, objpred->npar) != 0)
+	if (builder->tokenize(successor, objsucc, objpred->npar, begin, end) != 0)
 	{
 		logMessage("fL_Rule", "processDefinition", LOG_ERROR, "Unable to process successor sequence");
@@ -353,5 +437,5 @@
 {
 	// tokenize axiom
-	if (tokenize(axiom, genotype, 0) != 0)
+	if (tokenize(axiom, genotype, 0, begin, end) != 0)
 	{
 		logMessage("fL_Builder", "processDefinition", LOG_ERROR, "Unable to process axiom sequence");
@@ -372,24 +456,24 @@
 	switch (type)
 	{
-	case fLElementType::TERM:
-	{
-		tab = fL_word_paramtab;
-		obj = new fL_Word();
-		break;
-	}
-	case fLElementType::INFO:
-	{
-		tab = fL_builder_paramtab;
-		obj = this;
-		break;
-	}
-	case fLElementType::RULE:
-	{
-		tab = fL_rule_paramtab;
-		obj = new fL_Rule(begin, end);
-		break;
-	}
-	default:
-		break;
+		case fLElementType::TERM:
+		{
+			tab = fL_word_paramtab;
+			obj = new fL_Word();
+			break;
+		}
+		case fLElementType::INFO:
+		{
+			tab = fL_builder_paramtab;
+			obj = this;
+			break;
+		}
+		case fLElementType::RULE:
+		{
+			tab = fL_rule_paramtab;
+			obj = new fL_Rule(begin, end);
+			break;
+		}
+		default:
+			break;
 	}
 	Param par(tab);
@@ -417,10 +501,10 @@
 	stick->name = "S";
 	stick->npar = 8;
-	for (int i = 0; i < PART_PROPS_COUNT; i++)
+	for (int i = 0; i < FL_PART_PROPS_COUNT; i++)
 	{
 		stick->mut.addProperty(NULL, fL_part_names[i], "s", fL_part_fullnames[i], fL_part_fullnames[i], PARAM_CANOMITNAME, 0, -1);
 	}
 
-	for (int i = 0; i < JOINT_PROPS_COUNT; i++)
+	for (int i = 0; i < FL_JOINT_PROPS_COUNT; i++)
 	{
 		stick->mut.addProperty(NULL, fL_joint_names[i], "s", fL_joint_fullnames[i], fL_joint_fullnames[i], PARAM_CANOMITNAME, 0, -1);
@@ -430,4 +514,5 @@
 	stick->tab = ParamObject::makeParamTab((ParamInterface *)&stick->mut, 0, 0, stick->mut.firstMutableIndex());
 	words["S"] = stick;
+	wordnames.push_back("S");
 
 	// neuron N
@@ -435,7 +520,8 @@
 	neuron->name = "N";
 	neuron->npar = 1;
-	neuron->mut.addProperty(NULL, "d", "s", "details", "details", PARAM_CANOMITNAME, 0, -1);
+	neuron->mut.addProperty(NULL, "d", "s", "details", "details", 0, 0, -1);
 	neuron->tab = ParamObject::makeParamTab((ParamInterface *)&neuron->mut, 0, 0, neuron->mut.firstMutableIndex());
 	words["N"] = neuron;
+	wordnames.push_back("N");
 
 	// connection C
@@ -443,8 +529,9 @@
 	connection->name = "C";
 	connection->npar = 2;
-	connection->mut.addProperty(NULL, "w", "s", "weight", "weight", PARAM_CANOMITNAME, 0, -1);
-	connection->mut.addProperty(NULL, "attr", "s", "attractor", "connection attractor", PARAM_CANOMITNAME, 0, -1);
+	connection->mut.addProperty(NULL, FL_PE_CONN_WEIGHT, "s", "weight", "weight", PARAM_CANOMITNAME, 0, -1);
+	connection->mut.addProperty(NULL, FL_PE_CONN_ATTR, "s", "attractor", "connection attractor", PARAM_CANOMITNAME, 0, -1);
 	connection->tab = ParamObject::makeParamTab((ParamInterface *)&connection->mut, 0, 0, connection->mut.firstMutableIndex());
 	words["C"] = connection;
+	wordnames.push_back("C");
 
 	// rotation objects
@@ -463,4 +550,9 @@
 	rotz->npar = 1;
 	rotz->processDefinition(this);
+
+	//fL_Branch *branch = new fL_Branch(fL_Branch::BranchType::OPEN, 0, 0);
+	//branch->processDefinition(this);
+
+	builtincount = words.size();
 }
 
@@ -521,4 +613,9 @@
 				return res;
 			}
+			if (obj == this)
+			{
+				begin = lastpos;
+				end = pos - 1;
+			}
 			res = obj->processDefinition(this);
 			if (res != 0)
@@ -546,4 +643,5 @@
 		for (int i = 0; i < npar; i++)
 		{
+			SString t(par.id(i));
 			if (parevals[i] != NULL)
 			{
@@ -667,4 +765,5 @@
 	}
 	getStringifiedProducts();
+	removeRedundantRules();
 	Param par(fL_builder_paramtab);
 	fL_Builder *obj = new fL_Builder();
@@ -681,5 +780,5 @@
 }
 
-int fL_Rule::deploy(fL_Word *in, std::list<fL_Word *>::iterator &it, std::list<fL_Word *> &genotype, double currtime)
+int fL_Rule::deploy(fL_Builder *builder, fL_Word *in, std::list<fL_Word *>::iterator &it, double currtime)
 {
 	// if predecessor and given word differ, then rule is not applicable
@@ -720,9 +819,9 @@
 
 	// remove predecessor word from genotype and replace it with successor
-	it = genotype.erase(it);
+	it = builder->genotype.erase(it);
 	for (std::list<fL_Word *>::iterator word = objsucc.begin(); word != objsucc.end(); word++)
 	{
 		// create new word and copy properties from word definition
-		fL_Word *nword = new fL_Word();
+		fL_Word *nword = new fL_Word(false, begin, end);
 		*nword = **word;
 		// store information about when word has been created
@@ -735,11 +834,46 @@
 		// calculate word parameters and store MathEvaluation objects for further
 		// time manipulations.
+		Param par((*word)->tab, (*word)->data);
+		Param npar(nword->tab, nword->data);
 		for (int q = 0; q < nword->npar; q++)
 		{
 			if ((*word)->parevals[q] == NULL)
 			{
-				MathEvaluation *ev = new MathEvaluation(0);
-				ev->convertString("0");
-				nword->parevals.push_back(ev);
+				if ((*word)->builtin && (strcmp(npar.id(q), "d") == 0))
+				{
+					SString t = par.getString(q);
+					npar.setString(q, t);
+					nword->parevals.push_back(NULL);
+				}
+				if ((*word)->builtin && (strcmp(npar.id(q), FL_PE_CONN_ATTR) == 0))
+				{
+					SString t = par.getString(q);
+					if (t.len() > 0)
+					{
+						fL_Word *attrword = NULL;
+						builder->createWord(t, attrword, in->npar, begin, end);
+						for (int j = 0; j < attrword->npar; j++)
+						{
+							if (attrword->parevals[j])
+							{
+								for (int i = 0; i < in->npar; i++)
+								{
+									attrword->parevals[j]->modifyVariable(i, inwordvalues[i]);
+								}
+							}
+						}
+						SString res = attrword->stringify(false);
+						npar.setString(q, res);
+						nword->parevals.push_back(NULL);
+						delete attrword;
+					}
+				}
+				else
+				{
+					//MathEvaluation *ev = new MathEvaluation(0);
+					//ev->convertString("0");
+					//nword->parevals.push_back(ev);
+					nword->parevals.push_back(NULL);
+				}
 			}
 			else
@@ -757,5 +891,5 @@
 			}
 		}
-		genotype.insert(it, nword);
+		builder->genotype.insert(it, nword);
 	}
 	delete[] inwordvalues;
@@ -773,5 +907,5 @@
 		for (fL_Rule * rule : rules)
 		{
-			if (rule->deploy((*word), word, genotype, currtime) == 0)
+			if (rule->deploy(this, (*word), word, currtime) == 0)
 			{
 				deployed = true;
@@ -806,16 +940,20 @@
 	Param par(stickword->tab, stickword->data);
 	Param ppar = part->properties();
-	for (int i = 0; i < PART_PROPS_COUNT; i++)
-	{
-		double partprop;
+	for (int i = 0; i < FL_PART_PROPS_COUNT; i++)
+	{
+		double mn, mx, df;
+		ppar.getMinMaxDouble(ppar.findId(fL_part_names[i]), mn, mx, df);
 		double currval;
-		if (!ExtValue::parseDouble(par.getStringById(fL_part_names[i]).c_str(), currval, false))
-		{
-			logMessage("fL_Builder", "alterPartProperties", LOG_ERROR,
-				"Error parsing word parameter");
-			return 1;
-		}
-		partprop = (ppar.getDoubleById(fL_part_names[i]) * alterationcount +
-			currval) / (alterationcount + 1.0);
+		if (!stickword->parevals[i])
+		{
+			currval = df;
+		}
+		else
+		{
+			stickword->parevals[i]->evaluateRPN(currval);
+			currval = sigmoidTransform(currval, mn, mx);
+		}
+		double partprop = (ppar.getDoubleById(fL_part_names[i]) * alterationcount +
+				currval) / (alterationcount + 1.0);
 		ppar.setDoubleById(fL_part_names[i], partprop);
 	}
@@ -823,84 +961,88 @@
 }
 
-bool fL_Word::operator==(const fL_Word& other) const
-{
-	if (name == other.name) return false;
-	if (npar == other.npar) return false;
+double fL_Word::distance(fL_Word *right)
+{
+	if (name != right->name || npar != right->npar)
+	{
+		return -1;
+	}
+	double distance = 0;
 	for (int i = 0; i < npar; i++)
 	{
-		if (parevals[i] && other.parevals[i])
-		{
-			double first;
-			double second;
-			parevals[i]->evaluateRPN(first);
-			other.parevals[i]->evaluateRPN(second);
-			if (first != second)
-			{
-				return false;
-			}
-		}
-		else
-		{
-			return false;
-		}
-	}
-	return true;
-}
-
-void fL_Builder::findNext(fL_Word *word, std::list<fL_Word *> genotype, std::list<fL_Word *>::iterator &it)
-{
-	int rightdist = 0;
-	std::list<fL_Word *>::iterator iter(it);
-	for (; iter != genotype.end(); iter++)
-	{
-		if (*word == **iter)
-		{
-			break;
-		}
-		rightdist++;
-	}
-	int leftdist = 0;
-	std::list<fL_Word *>::reverse_iterator riter(it);
-	for (; riter != genotype.rend(); riter++)
-	{
-		if (*word == **riter)
-		{
-			break;
-		}
-		leftdist++;
-	}
-	if (iter == genotype.end())
-	{
-		it = riter.base();
-	}
-	else if (riter == genotype.rend())
-	{
-		it = iter;
-	}
-	else if (leftdist < rightdist)
-	{
-		it = riter.base();
+		double l = 0;
+		double r = 0;
+		if (parevals[i]) parevals[i]->evaluateRPN(l);
+		if (right->parevals[i]) right->parevals[i]->evaluateRPN(r);
+		distance += (l - r) * (l - r);
+	}
+	return sqrt(distance);
+}
+
+Neuro *fL_Builder::findInputNeuron(std::pair<std::list<fL_Word *>::iterator, Neuro *> currneu, fL_Word *attractor)
+{
+	if (!attractor)
+	{
+		std::list<fL_Word *>::reverse_iterator riter(currneu.first);
+		std::list<fL_Word *>::iterator iter(currneu.first);
+		iter++;
+		while (riter != genotype.rend() || iter != genotype.end())
+		{
+			if (iter != genotype.end())
+			{
+				if ((*iter)->name == "N" && (*iter)->bodyelementpointer != currneu.second)
+				{
+					return (Neuro *)(*iter)->bodyelementpointer;
+				}
+				iter++;
+			}
+			if (riter != genotype.rend())
+			{
+				if ((*riter)->name == "N" && (*riter)->bodyelementpointer != currneu.second)
+				{
+					return (Neuro *)(*riter)->bodyelementpointer;
+				}
+				riter++;
+			}
+		}
+		return NULL;
 	}
 	else
 	{
-		it = iter;
-	}
-}
-
-Neuro *fL_Builder::findInputNeuron(std::list<fL_Word *> genotype, std::list<fL_Word *>::iterator it, fL_Word *attractor)
-{
-	if (!attractor)
-	{
-		attractor = new fL_Word();
-		*attractor = *(*it);
-		attractor->data = NULL;
-		// TODO implement
+		double mindistance = -1;
+		std::list<fL_Word *>::iterator minit = genotype.end();
+		for (std::list<fL_Word *>::iterator it = genotype.begin(); it != genotype.end(); it++)
+		{
+			double currdist = attractor->distance((*it));
+			if (currdist != -1 && (currdist < mindistance || mindistance == -1))
+			{
+				mindistance = currdist;
+				minit = it;
+			}
+		}
+		if (minit != genotype.end())
+		{
+			for (; minit != genotype.end(); minit++)
+			{
+				if ((*minit)->name == "N" && (*minit)->bodyelementpointer)
+				{
+					Neuro *n = (Neuro *)(*minit)->bodyelementpointer;
+					if (n->getClass()->getPreferredOutput() != 0)
+					{
+						return n;
+					}
+				}
+			}
+		}
 	}
 	return NULL;
 }
 
-int fL_Builder::developModel(Model &model)
-{
-	// TODO implement
+double fL_Builder::sigmoidTransform(double input, double mn, double mx)
+{
+	return mn + (mx - mn) * (1.0 / (1.0 + exp(-input)));
+}
+
+int fL_Builder::buildModelFromSequence(Model *model)
+{
 	fL_State currstate;
 	std::unordered_map<Part *, double> counters;
@@ -923,5 +1065,6 @@
 						firstpart->p = Pt3D_0;
 						counters[firstpart] = 0;
-						model.addPart(firstpart);
+						model->addPart(firstpart);
+						if (using_mapping) firstpart->addMapping(IRange(word->begin, word->end));
 					}
 					currstate.currpart = firstpart;
@@ -941,32 +1084,52 @@
 				Param par(word->tab, word->data);
 				double length;
-				if (!ExtValue::parseDouble(par.getStringById("l").c_str(), length, false))
-				{
-					delete newpart;
-					logMessage("fL_Builder", "alterPartProperties", LOG_ERROR,
-						"Error parsing word parameter");
-					return 1;
+				if (!word->parevals[FL_PART_PROPS_COUNT + FL_JOINT_PROPS_COUNT])
+				{
+					length = FL_DEFAULT_LENGTH; // default length value
+				}
+				else
+				{
+					double parsedval = 0.0;
+					if (word->parevals[FL_PART_PROPS_COUNT + FL_JOINT_PROPS_COUNT]->evaluateRPN(parsedval) != 0)
+					{
+						delete newpart;
+						logMessage("fL_Builder", "developModel", LOG_ERROR,
+								"Error parsing word parameter");
+						return 1;
+					}
+					length = sigmoidTransform(parsedval, FL_MINIMAL_LENGTH, FL_MAXIMAL_LENGTH);
 				}
 				newpart->p = currstate.currpart->p + currstate.direction * length;
 				counters[newpart] += 1;
-				model.addPart(newpart);
-
+				model->addPart(newpart);
+				if (using_mapping) newpart->addMapping(IRange(word->begin, word->end));
 				Joint *newjoint = new Joint();
 				newjoint->attachToParts(currstate.currpart, newpart);
 
 				Param jpar = newjoint->properties();
-				for (int i = 0; i < JOINT_PROPS_COUNT; i++)
-				{
+				for (int i = 0; i < FL_JOINT_PROPS_COUNT; i++)
+				{
+					double mn, mx, df;
+					jpar.getMinMaxDouble(jpar.findId(fL_joint_names[i]), mn, mx, df);
 					double jointprop;
-					if (!ExtValue::parseDouble(par.getStringById(fL_joint_names[i]).c_str(), jointprop, false))
+					if (!word->parevals[FL_PART_PROPS_COUNT + i])
 					{
-						logMessage("fL_Builder", "developModel", LOG_ERROR,
-							"Error parsing word parameter");
-						delete newjoint;
-						return 1;
+						jointprop = df; // assign default value
+					}
+					else
+					{
+						if (word->parevals[FL_PART_PROPS_COUNT + i]->evaluateRPN(jointprop) != 0)
+						{
+							logMessage("fL_Builder", "developModel", LOG_ERROR,
+									"Error parsing word parameter");
+							delete newjoint;
+							return 1;
+						}
+						jointprop = sigmoidTransform(jointprop, mn, mx);
 					}
 					jpar.setDoubleById(fL_joint_names[i], jointprop);
 				}
-				model.addJoint(newjoint);
+				model->addJoint(newjoint);
+				if (using_mapping) newjoint->addMapping(IRange(word->begin, word->end));
 				currstate.currpart = newpart;
 			}
@@ -975,5 +1138,10 @@
 				Param npar(word->tab, word->data);
 				Neuro *neu = new Neuro();
-				neu->setDetails(npar.getStringById("d"));
+				SString details = npar.getStringById("d");
+				if (details == "")
+				{
+					details = "N";
+				}
+				neu->setDetails(details);
 				if (!neu->getClass())
 				{
@@ -982,33 +1150,46 @@
 					return 1;
 				}
-				model.addNeuro(neu);
-				currstate.currneuron = neu;
+				model->addNeuro(neu);
+				if (using_mapping) neu->addMapping(IRange(word->begin, word->end));
+				if (neu->getClass()->getPreferredInputs() != 0)
+				{
+					currstate.currneuron = neu;
+				}
+				word->bodyelementpointer = neu;
 			}
 			else if (word->name == "C")
 			{
-				connsbuffer.push_back({ w, currstate.currneuron });
+				connsbuffer.push_back({w, currstate.currneuron});
 			}
 			else if (word->name.startsWith("rot"))
 			{
-				Orient rotmatrix;
+				Orient rotmatrix = Orient_1;
 				double rot;
-				if (word->parevals[0]->evaluateRPN(rot) != 0)
-				{
-					logMessage("fL_Builder", "developModel", LOG_ERROR, "Error parsing neuron class");
+				if (!word->parevals[0])
+				{
+					rot = 0;
+				}
+				else if (word->parevals[0]->evaluateRPN(rot) != 0)
+				{
+					logMessage("fL_Builder", "developModel", LOG_ERROR, "Error parsing rotation word");
 					return 1;
 				}
+
+				rot = sigmoidTransform(rot, -M_PI, M_PI);
+
 				if (word->name == "rotX")
 				{
-					rotmatrix.rotate(Pt3D(rot, 0.0, 0.0));
+					rotmatrix.rotate(Pt3D(rot,0,0));
 				}
 				else if (word->name == "rotY")
 				{
-					rotmatrix.rotate(Pt3D(0.0, rot, 0.0));
+					rotmatrix.rotate(Pt3D(0,rot,0));
 				}
 				else if (word->name == "rotZ")
 				{
-					rotmatrix.rotate(Pt3D(0.0, 0.0, rot));
+					rotmatrix.rotate(Pt3D(0,0,rot));
 				}
 				currstate.direction = rotmatrix.transform(currstate.direction);
+				currstate.direction.normalize();
 			}
 			else if (word->name == "[")
@@ -1025,18 +1206,83 @@
 
 	// connections need
-	//	for (std::pair<std::list<fL_Word *>::iterator, Neuro *> conndata : connsbuffer)
-	//	{
-	//
-	//	}
+	// std::pair<std::list<fL_Word *>::iterator, Neuro *> conndata : connsbuffer
+	for (unsigned int i = 0; i < connsbuffer.size(); i++)
+	{
+		if (connsbuffer[i].second == NULL ||
+				(connsbuffer[i].second->getClass()->getPreferredInputs() != -1 &&
+				connsbuffer[i].second->getInputCount() >=
+				connsbuffer[i].second->getClass()->getPreferredInputs()))
+		{
+			// since connections are separated entities from neurons, it may happen
+			// that there will be no neuron to connect to
+			// logMessage("fL_Builder", "developModel", LOG_DEBUG, "Connection could not be established");
+		}
+		else
+		{
+			Param par((*connsbuffer[i].first)->tab, (*connsbuffer[i].first)->data);
+			SString attr = par.getStringById(FL_PE_CONN_ATTR);
+			fL_Word *attractor = NULL;
+			if (attr.len() > 0)
+			{
+				createWord(attr, attractor, 0, (*connsbuffer[i].first)->begin, (*connsbuffer[i].first)->end);
+			}
+			Neuro *neu = findInputNeuron(connsbuffer[i], attractor);
+			double weight = 0.0;
+			if ((*connsbuffer[i].first)->parevals[0])
+			{
+				if ((*connsbuffer[i].first)->parevals[0]->evaluateRPN(weight) != 0)
+				{
+					logMessage("fL_Builder", "developModel", LOG_ERROR,
+							"Error parsing word parameter");
+					delete attractor;
+					return 1;
+				}
+			}
+			if (neu)
+			{
+				connsbuffer[i].second->addInput(neu, weight);
+				if (using_mapping) neu->addMapping(
+						IRange((*connsbuffer[i].first)->begin,
+								(*connsbuffer[i].first)->end));
+			}
+			else
+			{
+				connsbuffer[i].second->addInput(connsbuffer[i].second, weight);
+				if (using_mapping) neu->addMapping(
+						IRange((*connsbuffer[i].first)->begin,
+								(*connsbuffer[i].first)->end));
+			}
+			delete attractor;
+		}
+	}
 	return 0;
 }
 
-int fL_Builder::develop()
-{
-	Model m;
+void fL_Builder::clearModelElements(Model *m)
+{
+	for (int i = 0; i < m->getJointCount(); i++)
+	{
+		m->removeJoint(i, 0);
+	}
+	for (int i = 0; i < m->getNeuroCount(); i++)
+	{
+		m->removeNeuro(i, true);
+	}
+	for (int i = 0; i < m->getNeuroCount(); i++)
+	{
+		m->removePart(i, 0, 0);
+	}
+	m->clearMap();
+}
+
+Model* fL_Builder::developModel(double &neededtime)
+{
 	double curriter = 0;
 	double timestamp = 1.0 / numckp;
 	double t = 0;
-	for (; t <= time; t += timestamp)
+	Model *m = new Model();
+	m->open(using_checkpoints);
+	bool wordsexceeded = false;
+	for (; t <= time; t+= timestamp)
 	{
 		alterTimedProperties(t); // always alter timed properties in the beginning
@@ -1045,7 +1291,32 @@
 		{
 			iterate(t);
-			curriter += 1.0;
-		}
-	}
+			curriter+=1.0;
+		}
+		if (using_checkpoints)
+		{
+			clearModelElements(m);
+			if (buildModelFromSequence(m) != 0)
+			{
+				delete m;
+				return NULL;
+			}
+			m->checkpoint();
+		}
+		if (maxwords != -1 && genotype.size() > (unsigned int)maxwords)
+		{
+			wordsexceeded = true;
+			break;
+		}
+	}
+
+	if (wordsexceeded)
+	{
+		neededtime = t;
+	}
+	else
+	{
+		neededtime = time;
+	}
+
 	// if exact time of development was not reached due to floating point errors,
 	// then alter timed properties
@@ -1054,4 +1325,86 @@
 		alterTimedProperties(time);
 	}
-	return 0;
-}
+	clearModelElements(m);
+	if (buildModelFromSequence(m) != 0)
+	{
+		delete m;
+		return NULL;
+	}
+	if (using_checkpoints)
+	{
+		m->checkpoint();
+	}
+	m->close();
+	return m;
+}
+
+int fL_Builder::countSticksInSequence(std::list<fL_Word *> sequence)
+{
+	int count = 0;
+	for (std::list<fL_Word *>::iterator it = sequence.begin(); it != sequence.end(); it++)
+	{
+		if ((*it)->builtin && (*it)->name == "S")
+		{
+			count++;
+		}
+	}
+	return count;
+}
+
+int fL_Builder::countDefinedWords()
+{
+	return words.size() - builtincount;
+}
+
+int fL_Builder::countWordsInLSystem()
+{
+	int count = genotype.size();
+	for (fL_Rule *rul: rules)
+	{
+		count += rul->objsucc.size();
+	}
+	count += words.size();
+	return count;
+}
+
+void fL_Builder::removeRedundantRules()
+{
+	for (std::vector<fL_Rule *>::iterator it = rules.begin();
+			it != rules.end(); it++)
+	{
+		std::vector<fL_Rule *>::iterator it2 = it;
+		it2++;
+		while (it2 != rules.end())
+		{
+			bool todelete = false;
+			if ((*it)->objpred->name == (*it2)->objpred->name)
+			{
+				if ((*it)->condeval == NULL && (*it2)->condeval == NULL)
+				{
+					todelete = true;
+				}
+				else if ((*it)->condeval == NULL && (*it2)->condeval != NULL)
+				{
+					std::iter_swap(it, it2);
+				}
+				else if ((*it)->condeval != NULL && (*it2)->condeval != NULL)
+				{
+					if ((*it)->condeval->getStringifiedRPN() ==
+							(*it2)->condeval->getStringifiedRPN())
+					{
+						todelete = true;
+					}
+				}
+			}
+			if (todelete)
+			{
+				delete (*it2);
+				it2 = rules.erase(it2);
+			}
+			else
+			{
+				it2++;
+			}
+		}
+	}
+}
Index: /cpp/frams/genetics/fL/fL_general.h
===================================================================
--- /cpp/frams/genetics/fL/fL_general.h	(revision 796)
+++ /cpp/frams/genetics/fL/fL_general.h	(revision 797)
@@ -1,4 +1,7 @@
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 1999-2018  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
 #ifndef _FL_GENERAL_
-
 #define _FL_GENERAL_
 
@@ -22,14 +25,20 @@
 /** @name Constants used in fL methods */
 //@{
-#define PART_PROPS_COUNT   4 ///<Count of part properties
-#define JOINT_PROPS_COUNT  3 ///<Count of joint properties
-#define PE_NEURO_DET       "d" ///<Id of details type definition in f0_neuro_paramtab
-#define PE_CONN_WEIGHT     "w" ///<Id of weight type definition in f0_neuroconn_paramtab
+#define FL_PART_PROPS_COUNT   4 ///<Count of part properties
+#define FL_JOINT_PROPS_COUNT  3 ///<Count of joint properties
+#define FL_PE_NEURO_DET       "d" ///<Id of details type definition in f0_neuro_paramtab
+#define FL_PE_CONN_WEIGHT     "w" ///<Id of weight type definition in f0_neuroconn_paramtab
+#define FL_PE_CONN_ATTR       "attr" ///<Id of attractor of neural connection
+#define FL_DEFAULT_LENGTH     1.0 ///<Default length of a stick in fL encoding
+#define FL_MINIMAL_LENGTH     0.0 ///<Minimal length of a stick in fL encoding
+#define FL_MAXIMAL_LENGTH     2.0 ///<Maximal length of a stick in fL encoding
+#define FL_MAXITER           "100.0" ///<Maximal iteration available in fL
+#define FL_MAXPARAMS          "3" ///<Maximal number of user-parameters
 //@}
 
 extern const char *fL_part_names[];
 extern const char *fL_joint_names[];
-extern const char *fL_joint_fullnames[JOINT_PROPS_COUNT];
-extern const char *fL_part_fullnames[PART_PROPS_COUNT];
+extern const char *fL_joint_fullnames[FL_JOINT_PROPS_COUNT];
+extern const char *fL_part_fullnames[FL_PART_PROPS_COUNT];
 
 #define LSYSTEM_PARAM_TYPE "s" ///< standard type of L-System elements
@@ -46,5 +55,9 @@
 	fLElementType type; ///< type of fL_Element
 
-	fL_Element(fLElementType type) : type(type) {}
+	int begin; ///<beginning of the element definition in genotype
+	int end; ///<end of the element definition in genotype
+
+
+	fL_Element(fLElementType type) : type(type) { begin = end = 0; }
 	virtual ~fL_Element() { }
 
@@ -78,8 +91,9 @@
 	void *data; ///<pointer to properties of word
 	bool builtin; ///<determines if word is built-in (true) or not (false).
+	PartBase *bodyelementpointer; ///<helper field for built-in words
 	std::vector<MathEvaluation *> parevals; ///<stores MathEvaluation objects with parameter functions
 	double creationiter; ///<this helper field is used during L-System iterations and determines when word was created
-	fL_Word(bool builtin = false) :
-		fL_Element(fLElementType::TERM), mut("Word", "Properties"), //TODO change Word to Term everywhere
+	fL_Word(bool builtin = false, int begin = 0, int end = 0) :
+		fL_Element(fLElementType::TERM), mut("Word", "Properties"),
 		builtin(builtin)
 	{
@@ -89,4 +103,7 @@
 		data = NULL;
 		creationiter = 0;
+		this->begin = begin;
+		this->end = end;
+		bodyelementpointer = NULL;
 	}
 
@@ -102,5 +119,5 @@
 		for (MathEvaluation *ev : parevals)
 		{
-			delete ev;
+			if (ev) delete ev;
 		}
 		parevals.clear();
@@ -125,9 +142,4 @@
 
 	/**
-	 * TODO
-	 */
-	bool operator==(const fL_Word& other) const;
-
-	/**
 	 * Returns 'w:' line defining word in genotype.
 	 * @return string representation of word definition
@@ -150,4 +162,13 @@
 	 */
 	int saveEvals(bool keepformulas);
+
+	/**
+	 * Computes distance between two L-System words in genotype. Distance has sense
+	 * only when two words have the same name. Otherwise, returned value equals -1.
+	 * The distance is computed as Euclidean distance between words arguments.
+	 * @param right the second word
+	 * @return Euclidean distance between words in genotype or -1 if words have different name
+	 */
+	double distance(fL_Word *right);
 };
 
@@ -164,5 +185,5 @@
 	};
 	BranchType btype; ///< branch parenthesis type
-	fL_Branch(BranchType branchtype) : fL_Word()
+	fL_Branch(BranchType branchtype, int begin, int end) : fL_Word(true, begin, end)
 	{
 		type = fLElementType::BRANCH;
@@ -192,9 +213,5 @@
 	std::list<fL_Word *> objsucc; ///<objec representation of successors
 
-	int start; ///<beginning of the rule definition in genotype
-	int end; ///<end of the rule definition in genotype
-
-	fL_Rule(int start, int end) : fL_Element(fLElementType::RULE),
-		start(start), end(end)
+	fL_Rule(int begin, int end)  : fL_Element(fLElementType::RULE)
 	{
 		predecessor = "";
@@ -203,4 +220,6 @@
 		objpred = NULL;
 		condeval = NULL;
+		this->begin = begin;
+		this->end = end;
 	}
 
@@ -231,13 +250,16 @@
 	 * pointer and iterator of list and inserting successor sequence into list.
 	 * Final iterator value points to the first word after processed word.
+	 * @param builder builder containing current genotype
 	 * @param in word that is currently processed
 	 * @param it iterator of genotype list which determines position of current analysis
-	 * @param genotype current genotype. Words on the left of genotype are processed and possibly replaced by previous rules
 	 * @param currtime value representing continuous time value for rule
 	 * @return 0 if rule processed current word, 1 if rule is no applicable
 	 */
-	int deploy(fL_Word *in, std::list<fL_Word *>::iterator &it, std::list<fL_Word *> &genotype, double currtime);
-};
-
+	int deploy(fL_Builder *builder, fL_Word *in, std::list<fL_Word *>::iterator &it, double currtime);
+};
+
+/**
+ * Structure for holding current Turtle state.
+ */
 struct fL_State
 {
@@ -245,5 +267,5 @@
 	Part *currpart; ///<Latest created part
 	Neuro *currneuron; ///<Latest created neuron
-	fL_State() : direction(1, 0, 0), currpart(NULL), currneuron(NULL)
+	fL_State() : direction(1,0,0), currpart(NULL), currneuron(NULL)
 	{ }
 };
@@ -266,14 +288,23 @@
 	double time; ///<time of creature development
 	int numckp; ///<number of checkpoints per growth step - checkpoints are always created after rules deployment in integer steps, numckp determines how many checkpoints should be created within single step (to watch changes of parameters)
+	int maxwords; ///<maximum number of words that can
+	bool using_checkpoints; ///<true if checkpoints should be created, or false
+	bool using_mapping; ///<true if mappings should be created, or false
+
+	int builtincount; ///<number of built-in words
 
 	std::list<fL_Word *> genotype; ///<list of current words of genotype
 	std::unordered_map<std::string, fL_Word *> words; ///<map from string to words existing in L-System
+	std::vector<std::string> wordnames;
 	std::vector<fL_Rule *> rules; ///<rules available in system
 
-	fL_Builder() : fL_Element(fLElementType::INFO)
+	fL_Builder(bool using_mapping = false, bool using_checkpoints = false) : fL_Element(fLElementType::INFO),
+			using_checkpoints(using_checkpoints), using_mapping(using_mapping)
 	{
 		axiom = "";
 		time = 1.0;
 		numckp = 1;
+		builtincount = 0;
+		maxwords = -1;
 	}
 	~fL_Builder();
@@ -337,4 +368,15 @@
 
 	/**
+	 * Creates fL_Word object for a given token.
+	 * @param worddef string representing single word object in sequence
+	 * @param word a reference to a pointer of created word by this method
+	 * @param numparams number of parameters for a given sequence (usually number of rule predecessor's arguments)
+	 * @param begin the begin of word definition in genotype
+	 * @param end the end of word definition in genotype
+	 * @return 0 if conversion went successfully, 1 when there is a problem with parsing
+	 */
+	int createWord(SString worddef, fL_Word *&word, int numparams, int begin, int end);
+
+	/**
 	 * Helper function that converts input sequence into list of L-System words.
 	 * @param sequence input sequence of stringified word objects
@@ -343,5 +385,5 @@
 	 * @return 0 if tokenizing finished successfully, 1 otherwise
 	 */
-	int tokenize(SString sequence, std::list<fL_Word *> &result, int numparams);
+	int tokenize(SString sequence, std::list<fL_Word *> &result, int numparams, int begin, int end);
 
 	/**
@@ -363,6 +405,10 @@
 	/**
 	 * Developes L-System from given genotype and builds Framsticks Model from it.
-	 */
-	int develop();
+	 * When using_checkpoints is enabled, method generates checkpoint for each
+	 * step defined in timestamp.
+	 * @param neededtime reference to a time value after stopping development (usually it will be equal to time specified in the time field, unless the number of allowed words will be exceeded earlier)
+	 * @return final model from a fL genotype
+	 */
+	Model* developModel(double &neededtime);
 
 	/**
@@ -371,5 +417,5 @@
 	 * @return 0 if developing went successfully, 1 otherwise
 	 */
-	int developModel(Model &model);
+	int buildModelFromSequence(Model *model);
 
 	/**
@@ -386,17 +432,71 @@
 
 	/**
-	 * TODO
+	 * Alters part properties according to informations stored in stickword.
+	 * Method takes current values of part's properties computed from previous
+	 * calls of the alterPartProperties and computes mean according to upcoming
+	 * values of properties.
+	 * @param part analysed part
+	 * @param stickword the L-System word that affects current part
+	 * @param alterationcount the number of times of part modifications - used to compute mean of every word properties
+	 * @return always 0
 	 */
 	int alterPartProperties(Part *part, fL_Word *stickword, double &alterationcount);
 
 	/**
-	 * TODO
-	 */
-	Neuro *findInputNeuron(std::list<fL_Word *> genotype, std::list<fL_Word *>::iterator it, fL_Word *attractor);
-
-	/**
-	 * TODO
-	 */
-	void findNext(fL_Word *word, std::list<fL_Word *> genotype, std::list<fL_Word *>::iterator &it);
+	 * Finds input neuron that is nearest to attractor or connection definition.
+	 * When attractor object is given, then the word with name matching attractor and
+	 * with nearest values of parameters is chosen as the point, from which input
+	 * neuron is looked for. The searching goes both sides.
+	 * @param currneu object storing informations about connection word iterator and current neuron
+	 * @param attractor pointer to an attractor definition presented in connection word
+	 * @return pointer to the input neuron, or NULL if no neuron could be found
+	 */
+	Neuro *findInputNeuron(std::pair<std::list<fL_Word *>::iterator, Neuro *> currneu, fL_Word *attractor);
+
+	/**
+	 * Removes joints, parts and neurons with its connections from current model, without
+	 * removing checkpoint data.
+	 * @param m model to clear
+	 */
+	void clearModelElements(Model *m);
+
+	/**
+	 * Converts parameters defined in built-in words into desired
+	 * range with use of sigmoid function, which ensures staying
+	 * within min and max value.
+	 * @param input the value from evaluation of parameter
+	 * @param min minimal value of property
+	 * @param max maximal value of property
+	 * @return value of body element property
+	 */
+	double sigmoidTransform(double input, double min, double max);
+
+	/**
+	 * Counts words defined in the genotype.
+	 * @return number of defined words
+	 */
+	int countDefinedWords();
+
+	/**
+	 * Counts number of sticks in a given sequence
+	 * @return number of sticks in sequence
+	 */
+	int countSticksInSequence(std::list<fL_Word *> sequence);
+
+	/**
+	 * Counts all definitions of words, all words in axiom and rule successors in a genotype.
+	 * Used for computing change between original genotype and mutation.
+	 * @return number of words in definitions, axiom and rule successors
+	 */
+	int countWordsInLSystem();
+
+	/**
+	 * Sorts rules and removes rules that will not be used. The "sorting" is
+	 * done in such way that all rules with conditions are first, and rules
+	 * without conditions go to the end. If there are more than one rule
+	 * for the same word with no condition or same condition, than the
+	 * second one is removed.
+	 */
+	void removeRedundantRules();
 };
 
Index: /cpp/frams/genetics/fL/fL_matheval.cpp
===================================================================
--- /cpp/frams/genetics/fL/fL_matheval.cpp	(revision 796)
+++ /cpp/frams/genetics/fL/fL_matheval.cpp	(revision 797)
@@ -1,6 +1,11 @@
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 1999-2018  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
 #include <frams/util/extvalue.h>
 #include <frams/util/sstring.h>
 #include <stack>
 #include "fL_matheval.h"
+#include <frams/genetics/genooperators.h>
 
 // Used available operators in MathEvaluation
@@ -94,18 +99,26 @@
 }
 
+void MathEvaluation::registerOperator(double(*operation)(double left, double right), int precedence, MathEvaluation::Associativity assoc, std::string opsymbol)
+{
+	operators[opsymbol] = new Operator(operation, precedence, assoc, opsymbol);
+	operatorstrings.push_back(opsymbol);
+}
+
 void MathEvaluation::registerOperators()
 {
 	// list of available operators in MathEvaluation
-	operators["+"] = new Operator(madd, 2, Associativity::LEFT, "+");
-	operators["-"] = new Operator(msub, 2, Associativity::LEFT, "-");
-	operators["*"] = new Operator(mmul, 3, Associativity::LEFT, "*");
-	operators["&"] = new Operator(mand, 0, Associativity::LEFT, "&");
-	operators["|"] = new Operator(mor, 0, Associativity::LEFT, "|");
-	operators[">"] = new Operator(mgreater, 1, Associativity::LEFT, ">");
-	operators["<"] = new Operator(mless, 1, Associativity::LEFT, "<");
-	operators[">="] = new Operator(meqgreater, 1, Associativity::LEFT, ">=");
-	operators["<="] = new Operator(meqless, 1, Associativity::LEFT, "<=");
-	operators["="] = new Operator(mequal, 1, Associativity::RIGHT, "=");
-	operators["~"] = new Operator(mnotequal, 1, Associativity::RIGHT, "~");
+	registerOperator(madd, 2, Associativity::LEFT, "+");
+	registerOperator(msub, 2, Associativity::LEFT, "-");
+	registerOperator(mmul, 3, Associativity::LEFT, "*");
+	registerOperator(mgreater, 1, Associativity::LEFT, ">");
+	registerOperator(mless, 1, Associativity::LEFT, "<");
+	registerOperator(meqgreater, 1, Associativity::LEFT, ">=");
+	registerOperator(meqless, 1, Associativity::LEFT, "<=");
+	registerOperator(mequal, 1, Associativity::RIGHT, "=");
+	registerOperator(mnotequal, 1, Associativity::RIGHT, "~");
+	registerOperator(mand, 0, Associativity::LEFT, "&");
+	registerOperator(mor, 0, Associativity::LEFT, "|");
+	arithmeticoperatorscount = 3;
+	comparisonoperatorscount = 6;
 }
 
@@ -159,4 +172,5 @@
 int MathEvaluation::convertString(std::string expression)
 {
+	originalexpression = expression;
 	clearPostfix(); //clear previous objects
 	ExtValue val;
@@ -193,6 +207,6 @@
 					{
 						delete operatorstack.back();
-						operatorstack.pop_back();
-					}
+					}
+					operatorstack.pop_back();
 				}
 				return -1;
@@ -247,6 +261,6 @@
 					{
 						delete operatorstack.back();
-						operatorstack.pop_back();
-					}
+					}
+					operatorstack.pop_back();
 				}
 				return -1;
@@ -407,6 +421,6 @@
 					{
 						delete operatorstack.back();
-						operatorstack.pop_back();
-					}
+					}
+					operatorstack.pop_back();
 				}
 				return -1;
@@ -434,6 +448,6 @@
 				{
 					delete operatorstack.back();
-					operatorstack.pop_back();
-				}
+				}
+				operatorstack.pop_back();
 			}
 			return -1;
@@ -448,5 +462,4 @@
 	// stack holds number used during operator execution
 	std::stack<Number *> numberstack;
-
 	for (std::list<Token *>::iterator it = postfixlist.begin(); it != postfixlist.end(); it++)
 	{
@@ -532,4 +545,9 @@
 int MathEvaluation::RPNToInfix(std::string &result)
 {
+	if (postfixlist.size() == 0)
+	{
+		result = "";
+		return 0;
+	}
 	// stack holds stringified chunk and its precedence
 	std::stack<std::pair<std::string, int>> chunks;
@@ -585,5 +603,5 @@
 	{
 		logMessage("MathEvaluation", "RPNToInfix", LOG_ERROR,
-			"Could not convert RPN notation to infix notation");
+			"Could not convert RPN notation to infix notation - formula is not complete");
 		return -1;
 	}
@@ -592,2 +610,199 @@
 	return 0;
 }
+
+void MathEvaluation::mutateValueOrVariable(MathEvaluation::Number *&currval, bool usetime)
+{
+	if (randomN(2) == 0 && varcount > 0) // use variable
+	{
+		if (currval && currval->type == TokenType::NUMBER)
+		{
+			delete currval;
+		}
+		int var = randomN(varcount + (usetime ? 1 : 0));
+		if (varcount == var) // time is used
+		{
+			currval = t;
+		}
+		else
+		{
+			currval = vars[var];
+		}
+	}
+	else
+	{
+		if (!currval || currval->type == TokenType::VARIABLE)
+		{
+			currval = new Number(rnd01);
+		}
+		else
+		{
+			currval->value = rnd01;
+		}
+	}
+}
+
+MathEvaluation::Operator* MathEvaluation::getRandomOperator(int type)
+{ // 0 for all, 1 for arithmetic only, 2 for logical only
+	int randop = type == 2 ? arithmeticoperatorscount : 0;
+	int count = arithmeticoperatorscount;
+	if (type == 0)
+	{
+		count = operatorstrings.size();
+	}
+	else if (type == 2)
+	{
+		count = operatorstrings.size() - arithmeticoperatorscount;
+	}
+	randop += randomN(count);
+	return operators[operatorstrings[randop]];
+}
+
+void MathEvaluation::mutateConditional()
+{
+	if (varcount > 0)
+	{
+		int currsize = postfixlist.size();
+		int varid = randomN(varcount);
+		postfixlist.push_back(vars[varid]);
+		if (randomN(2) == 0 && varcount > 1)
+		{
+			int varid2 = randomN(varcount - 1);
+			if (varid2 >= varid) varid2++;
+			postfixlist.push_back(vars[varid2]);
+		}
+		else
+		{
+			Number *num = new Number(rnd01);
+			postfixlist.push_back(num);
+		}
+		int opid = arithmeticoperatorscount + randomN(comparisonoperatorscount);
+		postfixlist.push_back(operators[operatorstrings[opid]]);
+		if (currsize > 0)
+		{
+			postfixlist.push_back(operators["&"]);
+		}
+	}
+}
+
+int MathEvaluation::mutate(bool logical, bool usetime)
+{
+	if (postfixlist.size() == 0)
+	{
+		Number *val = new Number(rnd01);
+		postfixlist.push_back(val);
+		return -1;
+	}
+	int method = randomN(postfixlist.size() < MAX_MUT_FORMULA_SIZE ? MATH_MUT_COUNT : MATH_MUT_COUNT - 1);
+	switch (method)
+	{
+	case MATH_MUT_INSERTION:
+	{
+		std::list<Token *> insertpair;
+		Number *val = NULL;
+		mutateValueOrVariable(val, usetime);
+		insertpair.push_back(val);
+		std::list<Token *>::iterator it = postfixlist.begin();
+		// insertion can be applied from 1st occurrence
+		int insertlocation = 1 + randomN(postfixlist.size() - 1);
+		std::advance(it, insertlocation);
+		Operator *rndop;
+		if (insertlocation == (int)postfixlist.size() - 1)
+		{
+			rndop = getRandomOperator(logical ? 2 : 1);
+		}
+		else
+		{
+			rndop = getRandomOperator(logical ? 0 : 1);
+
+		}
+		insertpair.push_back(rndop);
+		postfixlist.insert(it, insertpair.begin(), insertpair.end());
+		break;
+	}
+	case MATH_MUT_CHANGEVAL:
+	{
+		std::vector<std::list<Token *>::iterator> numbersineval;
+		int id = 0;
+		for (std::list<Token *>::iterator it = postfixlist.begin(); it != postfixlist.end(); it++)
+		{
+			if ((*it)->type == TokenType::NUMBER || (*it)->type == TokenType::VARIABLE)
+			{
+				numbersineval.push_back(it);
+			}
+			id++;
+		}
+		int randid = randomN(numbersineval.size());
+		Number *numptr = (Number *)(*numbersineval[randid]);
+		mutateValueOrVariable(numptr, usetime);
+		(*numbersineval[randid]) = numptr;
+		break;
+	}
+	case MATH_MUT_CHANGEOPER:
+	{
+		std::vector<std::list<Token *>::iterator> ops;
+		for (std::list<Token *>::iterator it = postfixlist.begin(); it != postfixlist.end(); it++)
+		{
+			if ((*it)->type == TokenType::OPERATOR)
+			{
+				ops.push_back(it);
+			}
+		}
+		if (ops.size() > 0)
+		{
+			int randid = randomN(ops.size());
+			Operator *rndop;
+			if (randid == (int)ops.size() - 1)
+			{
+				rndop = getRandomOperator(logical ? 2 : 1);
+			}
+			else
+			{
+				rndop = getRandomOperator(logical ? 0 : 1);
+			}
+			(*ops[randid]) = rndop;
+		}
+		break;
+	}
+	case MATH_MUT_DELETION:
+	{
+		std::list<Token *>::iterator it = postfixlist.begin();
+		std::vector<std::list<Token *>::iterator> firstofpairs;
+		while (it != postfixlist.end())
+		{
+			if ((*it)->type == TokenType::NUMBER || (*it)->type == TokenType::VARIABLE)
+			{
+				std::list<Token *>::iterator next = it;
+				next++;
+				if (next != postfixlist.end() && (*next)->type == TokenType::OPERATOR)
+				{
+					firstofpairs.push_back(it);
+				}
+			}
+			it++;
+		}
+		if (firstofpairs.size() > 0)
+		{
+			int rndid = randomN(firstofpairs.size());
+			if ((*firstofpairs[rndid])->type == TokenType::NUMBER)
+			{
+				delete (*firstofpairs[rndid]);
+			}
+			firstofpairs[rndid] = postfixlist.erase(firstofpairs[rndid]);
+			postfixlist.erase(firstofpairs[rndid]);
+		}
+		break;
+	}
+	}
+	return method;
+}
+
+std::string MathEvaluation::getStringifiedRPN()
+{
+	std::string res = "";
+	for (Token *el: postfixlist)
+	{
+		res += el->toString();
+		res += " ";
+	}
+	return res;
+}
Index: /cpp/frams/genetics/fL/fL_matheval.h
===================================================================
--- /cpp/frams/genetics/fL/fL_matheval.h	(revision 796)
+++ /cpp/frams/genetics/fL/fL_matheval.h	(revision 797)
@@ -1,2 +1,6 @@
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 1999-2018  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
 #ifndef _FL_MATHEVAL_H_
 #define _FL_MATHEVAL_H_
@@ -8,4 +12,13 @@
 
 #define VARIABLEPREFIX '$'
+
+#define MATH_MUT_DELETION   0
+#define MATH_MUT_CHANGEVAL  1
+#define MATH_MUT_CHANGEOPER 2
+#define MATH_MUT_INSERTION  3
+#define MATH_MUT_COUNT      4
+
+#define MAX_MUT_FORMULA_SIZE 7
+#define XOVER_MAX_MIGRATED_RULES 3
 
 /** @name Available operators for MathEvaluation */
@@ -171,4 +184,5 @@
 	void registerOperators();
 
+	std::string originalexpression;
 	std::unordered_map<std::string, Operator *> operators; ///<map containing available operators for MathEvaluation
 	std::list<Token *> postfixlist; ///<list with tokens ordered as postfix (RPN) notation of the given string
@@ -176,4 +190,7 @@
 	std::vector<Variable *> vars; ///<vector of variables, default values are 0
 	Variable *t; ///<additional t variable, that can act as time for evaluation
+	std::vector<std::string> operatorstrings; ///<list of registered operators
+	int arithmeticoperatorscount; ///<count of arithmetic operators
+	int comparisonoperatorscount;
 public:
 
@@ -226,4 +243,13 @@
 
 	/**
+	 * Registers new operator for MathEvaluation object.
+	 * @param operation function that needs to be called during evaluation
+	 * @param precedence precedence of operator
+	 * @param assoc associativity of the operator
+	 * @param opsymbol symbol of the operator
+	 */
+	void registerOperator(double(*operation)(double left, double right), int precedence, MathEvaluation::Associativity assoc, std::string opsymbol);
+
+	/**
 	 * Converts stored RPN list to infix string.
 	 * @param result reference to variable that will hold the result
@@ -231,4 +257,37 @@
 	 */
 	int RPNToInfix(std::string &result);
+
+	/**
+	 * Mutates formula.
+	 * @param logical if true, method uses logical operators only
+	 * @param usetime if true, method may use time variable during mutation
+	 * @param 0 if mutation could be performed, -1 if postfixlist is empty
+	 */
+	int mutate(bool logical, bool usetime);
+
+	/**
+	 * Mutates formula so it creates conjuctive of comparisons.
+	 */
+	void mutateConditional();
+
+	/**
+	 * Mutates number by using random value or one of variables.
+	 * @param currval current value of number
+	 * @param usetime if true, then time variable may be used
+	 */
+	void mutateValueOrVariable(Number *&currval, bool usetime);
+
+	/**
+	 * Returns formula in RPN notation
+	 * @param RPN notation string
+	 */
+	std::string getStringifiedRPN();
+
+	/**
+	 * Returns random operator available in object.
+	 * @param type 0 for all operators, 1 for arithmetic only, 2 for logical only
+	 * @return pointer to random operator
+	 */
+	Operator* getRandomOperator(int type);
 };
 
Index: /cpp/frams/genetics/fL/fL_oper.cpp
===================================================================
--- /cpp/frams/genetics/fL/fL_oper.cpp	(revision 796)
+++ /cpp/frams/genetics/fL/fL_oper.cpp	(revision 797)
@@ -1,9 +1,894 @@
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 1999-2018  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
 #include "fL_oper.h"
+#include <common/loggers/loggers.h>
+#include "../fH/fH_oper.h"
+#include <algorithm>
+
+#define FIELDSTRUCT Geno_fL
+static ParamEntry GENOfLparam_tab[] =
+{
+	{"Genetics: fL", 3, FL_OPCOUNT + FL_MUTGROUPSCOUNT + FL_CHG_COUNT + 2 + FL_ADD_COUNT, },
+	{"Genetics: fL: Probabilities of mutating axiom and rules", },
+	{"Genetics: fL: Probabilities of mutation types", },
+	{"fL_maxdefinedwords", 0, 0, "Maximum number of defined words", "d 0 100 10", FIELD(maxdefinedwords), "Maximum number of words that can be defined in L-System", },
+
+	{"fL_axm_mut_prob", 1, 0, "Axiom mutation", "f 0 1 0.2", FIELD(groupprobabilities[FL_AXM_WORD_MUT_PROB]), "Probability of performing mutation operations on axiom", },
+	{"fL_rul_mut_prob", 1, 0, "Rule's successor mutation", "f 0 1 0.8", FIELD(groupprobabilities[FL_RUL_WORD_MUT_PROB]), "Probability of performing mutation operations on the successor of random rule", },
+
+	{"fL_mut_addition", 2, 0, "Addition of word to sequence", "f 0 1 0.2", FIELD(operations[FL_ADD_WORD]), "Probability of adding random existing word to the axiom or one of successors", },
+
+	{"fL_mut_add_stick", 2, 0, " - addition of stick", "f 0 1 0.2", FIELD(addtypes[FL_ADD_STICK]), "Probability of adding stick", },
+	{"fL_mut_add_neuro", 2, 0, " - addition of neuron", "f 0 1 0.2", FIELD(addtypes[FL_ADD_NEURO]), "Probability of adding neuron", },
+	{"fL_mut_add_conn", 2, 0, " - addition of neuron connection", "f 0 1 0.2", FIELD(addtypes[FL_ADD_CONN]), "Probability of adding connection", },
+	{"fL_mut_add_rot", 2, 0, " - addition of rotation words", "f 0 1 0.2", FIELD(addtypes[FL_ADD_ROT]), "Probability of adding one of rotation words", },
+	{"fL_mut_add_branch", 2, 0, " - addition of branched stick", "f 0 1 0.2", FIELD(addtypes[FL_ADD_BRANCH]), "Probability of adding branch with rotation and stick", },
+	{"fL_mut_add_other", 2, 0, " - addition of defined words", "f 0 1 0.4", FIELD(addtypes[FL_ADD_OTHER]), "Probability of adding other word, defined in genotype", },
+
+	{"fL_mut_worddefaddition", 2, 0, "Addition of new word definition", "f 0 1 0.05", FIELD(operations[FL_ADD_WDEF]), "Probability of adding new word definition to the genotype", },
+	{"fL_mut_ruleaddition", 2, 0, "Addition of new rule definition", "f 0 1 0.1", FIELD(operations[FL_ADD_RULE]), "Probability of adding new rule definition for existing word", },
+	{"fL_mut_rulecond", 2, 0, "Modification of rule condition", "f 0 1 0.1", FIELD(operations[FL_CHG_COND]), "Probability of modifying random rule condition", },
+
+	{"fL_mut_changeword", 2, 0, "Change of random word", "f 0 1 0.3", FIELD(operations[FL_CHG_WORD]), "Probability of changing word name or formula of a random word from axiom or one of successors", },
+	{"fL_mut_changeword_formula", 2, 0, " - change of formula", "f 0 1 0.7", FIELD(chgoperations[FL_CHG_WORD_FORMULA]), "Probability of changing formula in word", },
+	{"fL_mut_changeword_name", 2, 0, " - change of name", "f 0 1 0.3", FIELD(chgoperations[FL_CHG_WORD_NAME]), "Probability of changing name in word", },
+
+	{"fL_mut_changeiter", 2, 0, "Change of L-System iteration", "f 0 1 0.3", FIELD(operations[FL_CHG_ITER]), "Probability of changing number of iterations of L-Systems", },
+	{"fL_mut_changeiter_step", 2, 0, "Step of iteration changing", "f 0 1 1.0", FIELD(iterchangestep), "Minimal step that should be used for changing iterations in L-Systems", },
+	{"fL_mut_deletion", 2, 0, "Deletion of random word", "f 0 1 0.2", FIELD(operations[FL_DEL_WORD]), "Probability of deleting random word from axiom or random successor (also deletes rule if there is only one word in successor)", },
+	{ 0, },
+};
+#undef FIELDSTRUCT
 
 Geno_fL::Geno_fL()
 {
-	//	par.setParamTab(GENOfHparam_tab);
-	//	par.select(this);
-	//	par.setDefault();
-	supported_format = 'H';
-}
+	par.setParamTab(GENOfLparam_tab);
+	par.select(this);
+	par.setDefault();
+	supported_format = 'L';
+	iterchangestep = 1.0;
+	maxdefinedwords = 10;
+}
+
+int Geno_fL::checkValidity(const char *geno, const char *genoname)
+{
+	LoggerToMemory eh(LoggerBase::Enable | LoggerToMemory::StoreAllMessages, LOG_WARN);
+	fL_Builder builder(false, false);
+
+	int err = builder.parseGenotype(geno);
+	if (err != 0)
+	{
+		return err;
+	}
+
+	if (builder.countSticksInSequence(builder.genotype) == 0)
+	{
+		return GENOPER_OPFAIL;
+	}
+	double neededtime = 0;
+	Model *m = builder.developModel(neededtime);
+	if (!m)
+	{
+		return GENOPER_OPFAIL;
+	}
+	if (!m->isValid())
+	{
+		delete m;
+		return GENOPER_OPFAIL;
+	}
+	delete m;
+
+
+	return GENOPER_OK;
+}
+
+int Geno_fL::validate(char *&geno, const char *genoname)
+{
+	LoggerToMemory eh(LoggerBase::Enable | LoggerToMemory::StoreAllMessages, LOG_WARN);
+	fL_Builder builder(false, false);
+
+	int err = builder.parseGenotype(geno);
+	if (err != 0)
+	{
+		return err;
+	}
+	double neededtime = 0;
+	Model *m = builder.developModel(neededtime);
+	if (!m->isValid())
+	{
+		delete m;
+		return GENOPER_OPFAIL;
+	}
+	if (neededtime != builder.time)
+	{
+		builder.time = neededtime;
+		free(geno);
+		geno = strdup(builder.toString().c_str());
+		delete m;
+		return GENOPER_OK;
+	}
+	delete m;
+	return GENOPER_OK;
+}
+
+bool Geno_fL::addWord(std::list<fL_Word *>* list, fL_Word *definition, std::list<fL_Word *>::iterator it)
+{
+	fL_Word *newword = new fL_Word();
+	*newword = *definition;
+
+	// if word has parameters
+	if (newword->npar > 0)
+	{
+		// create ParamObject that will hold parameter data
+		newword->data = ParamObject::makeObject(newword->tab);
+		Param par(newword->tab);
+		par.select(newword->data);
+		par.setDefault();
+		for (int i = 0; i < par.getPropCount(); i++)
+		{
+			newword->parevals.push_back(NULL);
+		}
+		if (newword->name.startsWith("rot"))
+		{
+			double rot = 2 * rnd01;
+			MathEvaluation *eval = new MathEvaluation(0);
+			eval->convertString(SString::valueOf(rot).c_str());
+			newword->parevals[0] = eval;
+		}
+		else if (newword->name == "N")
+		{
+			SString det;
+			NeuroClass *cls = getRandomNeuroClass();
+			if (!cls) cls = Neuro::getClass("N");
+			det = cls->getName();
+			Geno_fH::mutateNeuronProperties(det);
+			par.setStringById(FL_PE_NEURO_DET, det);
+		}
+		else if (newword->name == "C")
+		{
+			MathEvaluation *eval = new MathEvaluation(0);
+			eval->convertString(SString::valueOf(2 * rnd01 - 1).c_str());
+			newword->parevals[0] = eval;
+		}
+	}
+
+	list->insert(it, newword);
+	return true;
+}
+
+std::list<fL_Word *>* Geno_fL::selectRandomSequence(fL_Builder *creature, int &numparams, int &ruleid)
+{
+	std::list<fL_Word *> *list = NULL;
+	int axiomorrules = roulette(groupprobabilities, FL_MUTGROUPSCOUNT);
+	bool axiomused = axiomorrules == FL_AXM_WORD_MUT_PROB || creature->rules.size() == 0;
+	if (axiomused)
+	{
+		list = &creature->genotype;
+		numparams = 0;
+		ruleid = -1;
+	}
+	else
+	{
+		int rid = randomN(creature->rules.size());
+		list = &creature->rules[rid]->objsucc;
+		numparams = creature->rules[rid]->objpred->npar;
+		ruleid = rid;
+	}
+	return list;
+}
+
+fL_Word* Geno_fL::randomWordDefinition(fL_Builder *creature, int method)
+{
+	if (method == FL_ADD_OTHER && creature->builtincount < (int)creature->words.size())
+	{
+		return creature->words[creature->wordnames[creature->builtincount + randomN((int)creature->words.size() - creature->builtincount)]];
+	}
+	else
+	{
+		if (method == FL_ADD_OTHER) // we should be able to select stick, neuro or conn
+		{
+			double alttypes[FL_ADD_COUNT - 2];
+			alttypes[FL_ADD_STICK] = addtypes[FL_ADD_STICK];
+			alttypes[FL_ADD_NEURO] = addtypes[FL_ADD_NEURO];
+			alttypes[FL_ADD_CONN] = addtypes[FL_ADD_CONN];
+			alttypes[FL_ADD_ROT] = addtypes[FL_ADD_ROT];
+			method = roulette(alttypes, FL_ADD_COUNT - 2);
+		}
+		switch (method)
+		{
+		case FL_ADD_STICK:
+			return creature->words["S"];
+		case FL_ADD_NEURO:
+			return creature->words["N"];
+		case FL_ADD_CONN:
+			return creature->words["C"];
+		case FL_ADD_ROT:
+		{
+			int rottype = randomN(3);
+			switch (rottype)
+			{
+			case 0:
+				return creature->words["rotX"];
+			case 1:
+				return creature->words["rotY"];
+			case 2:
+				return creature->words["rotZ"];
+			}
+			break;
+		}
+		case FL_ADD_BRANCH:
+			// return NULL
+			break;
+		}
+	}
+	return NULL;
+}
+
+void Geno_fL::deleteBranch(std::list<fL_Word *> *list, std::list<fL_Word *>::iterator openbranchposition)
+{
+	fL_Branch *branch = (fL_Branch *)(*openbranchposition);
+	if (branch->btype == fL_Branch::BranchType::OPEN)
+	{
+		int bcount = 1;
+		delete (*openbranchposition);
+		openbranchposition = list->erase(openbranchposition);
+		for (; openbranchposition != list->end(); openbranchposition++)
+		{
+			if ((*openbranchposition)->type == fLElementType::BRANCH)
+			{
+				branch = (fL_Branch *)(*openbranchposition);
+				if (branch->btype == fL_Branch::BranchType::OPEN)
+				{
+					bcount++;
+				}
+				else
+				{
+					bcount--;
+					if (bcount == 0)
+					{
+						delete branch;
+						list->erase(openbranchposition);
+						break;
+					}
+				}
+			}
+		}
+	}
+	else
+	{
+		openbranchposition++;
+		if (openbranchposition != list->end())
+		{
+			delete (*openbranchposition);
+			list->erase(openbranchposition);
+		}
+	}
+}
+
+int Geno_fL::mutate(char *&geno, float& chg, int &method)
+{
+	fL_Builder *creature = new fL_Builder(false, false);
+
+	if (creature->parseGenotype(geno) != 0)
+	{
+		delete creature;
+		return GENOPER_OPFAIL;
+	}
+
+	int before = creature->countWordsInLSystem();
+
+	method = roulette(operations, FL_OPCOUNT);
+	switch (method)
+	{
+		case FL_CHG_ITER:
+		{
+			if (randomN(2) == 0)
+			{
+				creature->time = creature->time + iterchangestep <= ExtValue::getDouble(FL_MAXITER) ?
+						creature->time + iterchangestep : creature->time - iterchangestep;
+			}
+			else
+			{
+				creature->time = creature->time - iterchangestep >= 0 ?
+						creature->time - iterchangestep : creature->time + iterchangestep;
+			}
+			break;
+		}
+		case FL_CHG_COND:
+		{
+			if (creature->rules.size() > 0)
+			{
+				int ruleid = randomN(creature->rules.size());
+				if (!creature->rules[ruleid]->condeval)
+				{
+					creature->rules[ruleid]->condeval = new MathEvaluation(creature->rules[ruleid]->objpred->npar);
+				}
+				creature->rules[ruleid]->condeval->mutateConditional();
+				break;
+			}
+			// if there are no rules - create one
+		}
+		/* no break */
+		case FL_ADD_RULE:
+		{
+			std::unordered_map<std::string, fL_Word *>::iterator pred = creature->words.begin();
+			std::vector<fL_Word *> wordswithnorules;
+			for (; pred != creature->words.end(); pred++)
+			{
+				if (!pred->second->builtin)
+				{
+					bool norules = true;
+					for (fL_Rule * r : creature->rules)
+					{
+						if (pred->second->name == r->objpred->name &&
+								pred->second->npar == r->objpred->npar)
+						{
+							norules = false;
+							break;
+						}
+					}
+					if (norules)
+					{
+						wordswithnorules.push_back(pred->second);
+					}
+				}
+			}
+			if (wordswithnorules.size() > 0)
+			{
+				int predid = randomN(wordswithnorules.size());
+				fL_Rule *newrule = new fL_Rule(0,0);
+				fL_Word *pred = new fL_Word();
+				*pred = *wordswithnorules[predid];
+				newrule->objpred = pred;
+				fL_Word *initdef = randomWordDefinition(creature, roulette(addtypes, FL_ADD_COUNT - 1)); // -1 to avoid branching
+				addWord(&newrule->objsucc, initdef, newrule->objsucc.begin());
+				creature->rules.push_back(newrule);
+				break;
+			}
+			else if (creature->rules.size() > 0)
+			{
+				int ruleid = randomN(creature->rules.size());
+				fL_Rule *newrule = new fL_Rule(0, 0);
+				fL_Word *pred = new fL_Word();
+				*pred = *creature->rules[ruleid]->objpred;
+				newrule->objpred = pred;
+				if (creature->rules[ruleid]->condeval)
+				{
+					std::string formula = "";
+					creature->rules[ruleid]->condeval->RPNToInfix(formula);
+					if (formula.find("1.0-(") != 0)
+					{
+						std::string res = "1.0-(";
+						res += formula;
+						res += ")";
+						newrule->condeval = new MathEvaluation(pred->npar);
+						newrule->condeval->convertString(res);
+					}
+					else
+					{
+						newrule->condeval = new MathEvaluation(pred->npar);
+						newrule->condeval->mutateConditional();
+					}
+				}
+				else
+				{
+					newrule->condeval = new MathEvaluation(pred->npar);
+					newrule->condeval->mutateConditional();
+				}
+				fL_Word *worddef = randomWordDefinition(creature, roulette(addtypes, FL_ADD_COUNT - 1));
+				addWord(&newrule->objsucc, worddef, newrule->objsucc.begin());
+				creature->rules.push_back(newrule);
+				break;
+			}
+			// if there are no words, from which rules can be formed, then add one
+		}
+		/* no break */
+		case FL_ADD_WDEF:
+		{
+			if (creature->countDefinedWords() <= maxdefinedwords)
+			{
+				int npar = randomN(ExtValue::getInt(FL_MAXPARAMS, false));
+				for (int i = 0; i < maxdefinedwords; i++)
+				{
+					std::string name = "w";
+					name += std::to_string(i);
+					if (creature->words.find(name) == creature->words.end())
+					{
+						fL_Word *word = new fL_Word(false, 0, 0);
+						word->npar = npar;
+						word->name = name.c_str();
+						word->processDefinition(creature);
+						break;
+					}
+				}
+				break;
+			}
+			//no break at the end of case - if there is too many words, then
+			// deletion should be performed
+		}
+		/* no break */
+		case FL_DEL_WORD:
+		{
+			int numpars = 0;
+			int ruleid = 0;
+			std::list<fL_Word *> *list = selectRandomSequence(creature, numpars, ruleid);
+			if (ruleid == -1 && creature->countSticksInSequence((*list)) == 1)
+			{
+				if (list->size() > 1)
+				{
+					int rndid = randomN(list->size() - 1);
+					int j = 0;
+					std::list<fL_Word *>::iterator it = list->begin();
+					if ((*it)->name == "S")
+					{
+						it++;
+					}
+					while (it != list->end() && j < rndid && ((*it)->name == "S"))
+					{
+						if ((*it)->name != "S")
+						{
+							j++;
+						}
+						it++;
+					}
+					if (it != list->end())
+					{
+						if ((*it)->type == fLElementType::BRANCH)
+						{
+							deleteBranch(list, it);
+						}
+						else
+						{
+							delete (*it);
+							list->erase(it);
+						}
+						break;
+					}
+					// else add word
+				}
+				// else add word
+			}
+			else
+			{
+				int rndid = randomN(list->size());
+				std::list<fL_Word *>::iterator it = list->begin();
+				std::advance(it, rndid);
+				if ((*it)->type == fLElementType::BRANCH)
+				{
+					deleteBranch(list, it);
+				}
+				else
+				{
+					delete (*it);
+					list->erase(it);
+				}
+				if (ruleid > -1 && creature->rules[ruleid]->objsucc.size() == 0)
+				{
+					delete creature->rules[ruleid];
+					creature->rules.erase(creature->rules.begin() + ruleid);
+				}
+				break;
+			}
+			// if no words available, then add word
+		}
+		/* no break */
+		case FL_ADD_WORD:
+		{
+			int numpars = 0;
+			int tmp = 0;
+			std::list<fL_Word *> *list = selectRandomSequence(creature, numpars, tmp);
+			int rndid = randomN(list->size());
+			std::list<fL_Word *>::iterator it = list->begin();
+			std::advance(it, rndid);
+			int meth = roulette(addtypes, FL_ADD_COUNT);
+			if (tmp == -1)
+			{ // if sequence is axiom and it does not have non-builtin words
+				bool hasdefined = false;
+				for (std::list<fL_Word *>::iterator elem = list->begin(); elem != list->end(); elem++)
+				{
+					if (!(*elem)->builtin)
+					{
+						hasdefined = true;
+						break;
+					}
+				}
+				if (!hasdefined)
+				{
+					meth = FL_ADD_OTHER;
+				}
+
+			}
+			if (meth != FL_ADD_BRANCH)
+			{
+				fL_Word *worddef = randomWordDefinition(creature, meth);
+				addWord(list, worddef, it);
+			}
+			else
+			{
+				fL_Branch *start = new fL_Branch(fL_Branch::BranchType::OPEN, 0, 0);
+				list->insert(it, start);
+				int rottype = randomN(2);
+				switch (rottype)
+				{
+				case 0:
+					addWord(list, creature->words["rotY"], it);
+				case 1:
+					addWord(list, creature->words["rotZ"], it);
+				}
+				addWord(list, creature->words["S"], it);
+				fL_Branch *end = new fL_Branch(fL_Branch::BranchType::CLOSE, 0, 0);
+				list->insert(it, end);
+			}
+			break;
+		}
+		case FL_CHG_WORD:
+		{
+			int numpars = 0;
+			int tmp = 0;
+			std::list<fL_Word *> *list = selectRandomSequence(creature, numpars, tmp);
+			int rndid = randomN(list->size());
+			std::list<fL_Word *>::iterator selectedword = list->begin();
+			std::advance(selectedword, rndid);
+			if ((*selectedword)->type == fLElementType::BRANCH)
+			{
+				break;
+			}
+			int chgtype = roulette(chgoperations, FL_CHG_COUNT);
+			if (creature->countSticksInSequence((*list)) == 1 && tmp == -1) // if sequence is axiom
+			{
+				fL_Word *worddef = randomWordDefinition(creature, roulette(addtypes, FL_ADD_COUNT - 1));
+
+				int numpars = 0;
+				std::list<fL_Word *> *list = selectRandomSequence(creature, numpars, tmp);
+				int rndid = randomN(list->size());
+				std::list<fL_Word *>::iterator it = list->begin();
+				std::advance(it, rndid);
+
+				addWord(list, worddef, it);
+
+				break;
+			}
+			else if (chgtype == FL_CHG_WORD_NAME)
+			{
+				if ((*selectedword)->builtin)
+				{
+					delete (*selectedword);
+					selectedword = list->erase(selectedword);
+					fL_Word *worddef = randomWordDefinition(creature, roulette(addtypes, FL_ADD_COUNT - 1));
+					addWord(list, worddef, selectedword);
+				}
+				else
+				{
+					std::vector<fL_Word *> available;
+					for (std::unordered_map<std::string, fL_Word *>::iterator wit = creature->words.begin();
+							wit != creature->words.end(); wit++)
+					{
+						if ((*selectedword)->npar == wit->second->npar &&
+								(*selectedword)->name != wit->second->name &&
+								!wit->second->builtin)
+						{
+							available.push_back(wit->second);
+						}
+					}
+					if (available.size() > 0)
+					{
+						int newnameid = randomN(available.size());
+						(*selectedword)->name = available[newnameid]->name;
+					}
+					else
+					{
+						delete (*selectedword);
+						selectedword = list->erase(selectedword);
+						fL_Word *worddef = randomWordDefinition(creature, roulette(addtypes, FL_ADD_COUNT - 1));
+						addWord(list, worddef, selectedword);
+					}
+				}
+			}
+			else
+			{
+				if ((*selectedword)->npar > 0)
+				{
+					int randeval = randomN((*selectedword)->npar);
+					Param par((*selectedword)->tab, (*selectedword)->data);
+					if ((*selectedword)->builtin && (*selectedword)->name == "N"
+							&& strcmp(par.id(randeval), FL_PE_NEURO_DET) == 0)
+					{
+						SString res = par.getStringById(FL_PE_NEURO_DET);
+						Geno_fH::mutateNeuronProperties(res);
+						par.setStringById(FL_PE_NEURO_DET, res);
+					}
+					else if ((*selectedword)->builtin &&
+							(*selectedword)->name == "C" &&
+							strcmp(par.id(randeval), FL_PE_CONN_ATTR) == 0)
+					{
+						SString strattractor = par.getStringById(FL_PE_CONN_ATTR);
+						if (strattractor.len() > 0)
+						{
+							fL_Word *w = NULL;
+							creature->createWord(strattractor, w, numpars, 0, 0);
+							// mutate attractor parameter
+							if (w->npar > 0)
+							{
+								int rndattr = randomN(w->npar);
+								if (!w->parevals[rndattr])
+								{
+									w->parevals[rndattr] = new MathEvaluation(numpars);
+								}
+								w->parevals[rndattr]->mutate(false, false);
+							}
+							strattractor = w->stringify(true);
+							par.setStringById(FL_PE_CONN_ATTR, strattractor);
+							delete w;
+						}
+						else
+						{
+							if (creature->builtincount < (int)creature->words.size())
+							{
+								fL_Word *wdef = randomWordDefinition(creature, FL_ADD_OTHER);
+								fL_Word *w = new fL_Word();
+								*w = *wdef;
+								w->data = ParamObject::makeObject(w->tab);
+								Param apar(w->tab);
+								apar.select(w->data);
+								apar.setDefault();
+								if (w->npar > 0)
+								{
+									int rndattr = randomN(w->npar);
+									for (int i = 0; i < w->npar; i++)
+									{
+										if (i == rndattr)
+										{
+											MathEvaluation *ev = new MathEvaluation(numpars);
+											ev->mutate(false, false);
+											w->parevals.push_back(ev);
+										}
+										else
+										{
+											w->parevals.push_back(NULL);
+										}
+									}
+
+								}
+								strattractor = w->stringify(false);
+								par.setStringById(FL_PE_CONN_ATTR, strattractor);
+								delete w;
+							}
+						}
+					}
+					else
+					{
+						if (!(*selectedword)->parevals[randeval])
+						{
+							(*selectedword)->parevals[randeval] = new MathEvaluation(numpars);
+						}
+						(*selectedword)->parevals[randeval]->mutate(false, iterchangestep != 1.0);
+					}
+				}
+			}
+			break;
+		}
+	}
+
+	free(geno);
+	geno = strdup(creature->toString().c_str());
+	chg = (double)abs(before - creature->countWordsInLSystem()) / before;
+	delete creature;
+
+	return GENOPER_OK;
+}
+
+fL_Word* Geno_fL::getAppropriateWord(fL_Builder *from, fL_Builder *to, fL_Word *fromword, std::unordered_map<std::string, std::string> &map)
+{
+	if (fromword->name == "[" || fromword->name == "]") // if words are branching words
+	{
+		fL_Branch *newword = new fL_Branch(fromword->name == "[" ? fL_Branch::BranchType::OPEN : fL_Branch::BranchType::CLOSE, 0, 0);
+		return newword;
+	}
+	if (fromword->builtin)
+	{
+		fL_Word *newword = new fL_Word();
+		(*newword) = (*to->words[fromword->name.c_str()]);
+		return newword;
+	}
+	if (map.find(fromword->name.c_str()) != map.end()) // if word is already mapped
+	{
+		fL_Word *newword = new fL_Word();
+		(*newword) = (*to->words[map[fromword->name.c_str()]]);
+		return newword;
+	}
+	else if (to->words.find(fromword->name.c_str()) != to->words.end() &&
+			to->words[fromword->name.c_str()]->npar == fromword->npar) // if there is already same word with same number of parameters
+	{
+		fL_Word *newword = new fL_Word();
+		map[fromword->name.c_str()] = fromword->name.c_str();
+		(*newword) = (*to->words[map[fromword->name.c_str()]]);
+		return newword;
+	}
+	for (std::unordered_map<std::string, fL_Word *>::iterator it = to->words.begin();
+			it != to->words.end(); it++)
+	{ // find word with same number of parameters
+		if (fromword->npar == it->second->npar && map.find(fromword->name.c_str()) == map.end() && !it->second->builtin)
+		{ // if there is a word with same number of parameters
+			map[fromword->name.c_str()] = it->second->name.c_str();
+			fL_Word *newword = new fL_Word();
+			(*newword) = (*it->second);
+			return newword;
+		}
+	}
+	fL_Word *newworddef = new fL_Word();
+	(*newworddef) = (*fromword);
+	newworddef->parevals.clear();
+	if (to->words.find(newworddef->name.c_str()) != to->words.end())
+	{
+		int i = 0;
+		while (true)
+		{
+			std::string name = "w";
+			name += std::to_string(i);
+			if (to->words.find(name) == to->words.end())
+			{
+				newworddef->name = name.c_str();
+				break;
+			}
+			i++;
+		}
+	}
+	newworddef->processDefinition(to);
+	map[fromword->name.c_str()] = newworddef->name.c_str();
+	fL_Word *newword = new fL_Word();
+	(*newword) = (*to->words[map[fromword->name.c_str()]]);
+	return newword;
+}
+
+void Geno_fL::migrateRandomRules(fL_Builder *from, fL_Builder *to, int numselrules)
+{
+	std::unordered_map<std::string, std::string> map;
+	if (from->rules.size() > 0)
+	{
+		for (int i = 0; i < numselrules; i++)
+		{
+			int rulid = randomN(from->rules.size());
+			fL_Rule *rul = from->rules[rulid];
+			fL_Rule *newrule = new fL_Rule(0, 0);
+			newrule->objpred = getAppropriateWord(from, to, rul->objpred, map);
+			for (fL_Word *w : rul->objsucc)
+			{
+				fL_Word *el = getAppropriateWord(from, to, w, map);
+				if (el->type == fLElementType::BRANCH)
+				{
+					newrule->objsucc.push_back(el);
+					continue;
+				}
+				Param origpar(w->tab);
+				origpar.select(w->data);
+				el->data = ParamObject::makeObject(el->tab);
+				Param par(el->tab);
+				par.select(el->data);
+				par.setDefault();
+				for (int i = 0; i < el->npar; i++)
+				{
+					std::string form;
+					if (w->builtin && w->name == "N"
+							&& strcmp(par.id(i), FL_PE_NEURO_DET) == 0)
+					{
+						SString res = origpar.getStringById(FL_PE_NEURO_DET);
+						par.setStringById(FL_PE_NEURO_DET, res);
+						el->parevals.push_back(NULL);
+					}
+					else if (w->builtin && w->name == "C"
+							&& strcmp(par.id(i), FL_PE_CONN_ATTR) == 0)
+					{
+						SString strattractor = origpar.getStringById(FL_PE_CONN_ATTR);
+						if (strattractor.len() > 0)
+						{
+							fL_Word *tmp = NULL;
+							from->createWord(strattractor, tmp, newrule->objpred->npar, 0, 0);
+							fL_Word *newsuccword = getAppropriateWord(from, to, tmp, map);
+							newsuccword->data = ParamObject::makeObject(el->tab);
+							newsuccword->parevals = tmp->parevals;
+							tmp->parevals.clear();
+							strattractor = newsuccword->stringify(true);
+							par.setStringById(FL_PE_CONN_ATTR, strattractor);
+							delete newsuccword;
+							delete tmp;
+						}
+						par.setStringById(FL_PE_CONN_ATTR, strattractor);
+						el->parevals.push_back(NULL);
+					}
+					else if (w->parevals[i])
+					{
+						MathEvaluation *eval = new MathEvaluation(newrule->objpred->npar);
+						w->parevals[i]->RPNToInfix(form);
+						eval->convertString(form);
+						el->parevals.push_back(eval);
+					}
+					else
+					{
+						el->parevals.push_back(NULL);
+					}
+				}
+				newrule->objsucc.push_back(el);
+			}
+			to->rules.push_back(newrule);
+		}
+	}
+}
+
+int Geno_fL::crossOver(char *&g1, char *&g2, float& chg1, float& chg2)
+{
+	fL_Builder *creature1 = new fL_Builder(false, false);
+	fL_Builder *creature1template = new fL_Builder(false, false);
+	fL_Builder *creature2 = new fL_Builder(false, false);
+	fL_Builder *creature2template = new fL_Builder(false, false);
+
+	int count1 = creature1->countWordsInLSystem();
+	int count2 = creature2->countWordsInLSystem();
+
+	if (creature1->parseGenotype(g1) != 0 || creature2->parseGenotype(g2) != 0)
+	{
+		delete creature1;
+		delete creature2;
+		delete creature1template;
+		delete creature2template;
+		return GENOPER_OPFAIL;
+	}
+
+	creature1template->parseGenotype(g1);
+	creature2template->parseGenotype(g2);
+
+	int numselrules = 1 + randomN(XOVER_MAX_MIGRATED_RULES);
+	numselrules = numselrules < (int)creature1->rules.size() ? numselrules : (int)creature1->rules.size();
+
+	migrateRandomRules(creature1template, creature2, numselrules);
+
+	numselrules = 1 + randomN(XOVER_MAX_MIGRATED_RULES);
+	numselrules = numselrules < (int)creature1->rules.size() ? numselrules : (int)creature1->rules.size();
+
+	migrateRandomRules(creature2template, creature1, numselrules);
+
+	free(g1);
+	free(g2);
+
+	g1 = strdup(creature1->toString().c_str());
+	g2 = strdup(creature2->toString().c_str());
+
+	chg1 = (double)count1 / creature1->countWordsInLSystem();
+	chg1 = (double)count2 / creature2->countWordsInLSystem();
+
+	delete creature1;
+	delete creature2;
+	delete creature1template;
+	delete creature2template;
+
+	return GENOPER_OK;
+}
+
+uint32_t Geno_fL::style(const char *geno, int pos)
+{
+	char ch = geno[pos];
+	uint32_t style = GENSTYLE_CS(0, GENSTYLE_STRIKEOUT);
+	if (pos == 0 || geno[pos - 1] == '\n' || ch == ':') // single-character line definition
+	{
+		style = GENSTYLE_CS(GENCOLOR_TEXT, GENSTYLE_BOLD);
+	}
+	else if (strchr("()", ch) != NULL)
+	{
+		style = GENSTYLE_RGBS(50, 50, 50, GENSTYLE_BOLD);
+	}
+	else if (isalpha(ch)) // properties name
+	{
+		style = GENSTYLE_RGBS(0, 200, 0, GENSTYLE_BOLD);
+	}
+	else if (isdigit(ch) || strchr(",.=", ch)) // properties values
+	{
+		style = GENSTYLE_CS(GENCOLOR_TEXT, GENSTYLE_NONE);
+	}
+	else if (ch == '\"')
+	{
+		style = GENSTYLE_RGBS(200, 0, 0, GENSTYLE_BOLD);
+	}
+
+	return style;
+}
Index: /cpp/frams/genetics/fL/fL_oper.h
===================================================================
--- /cpp/frams/genetics/fL/fL_oper.h	(revision 796)
+++ /cpp/frams/genetics/fL/fL_oper.h	(revision 797)
@@ -1,24 +1,136 @@
-#ifndef _FL_OPER_
-#define _FL_OPER_
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 1999-2018  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
+#ifndef _FL_OPER_H_
+#define _FL_OPER_H_
 
 #include "../genooperators.h"
+#include "fL_general.h"
+
+/** @name Codes for general fL mutation types */
+//@{
+#define FL_ADD_WORD 0 ///<probability of adding word to axiom or rule successor
+#define FL_ADD_WDEF 1 ///<probability of adding word definition
+#define FL_ADD_RULE 2 ///<probability of adding new rule
+#define FL_CHG_COND 3 ///<probability of modification or rule condition
+#define FL_CHG_WORD 4 ///<probability of changing word name or formula in axiom or rule successor
+#define FL_CHG_ITER 5 ///<probability of changing iteration of genotype
+#define FL_DEL_WORD 6 ///<probability of deleting word from axiom or rule. Deleting all occurrences of word deletes word definition. Deleting all words in rule deletes this rule
+#define FL_OPCOUNT 7 ///<count of mutation operators
+//@}
+
+/** @name Codes for probabilities of mutating axiom words or rule words */
+//@{
+#define FL_AXM_WORD_MUT_PROB 0 ///<probability of performing addition, substitution or deletion of word in axiom
+#define FL_RUL_WORD_MUT_PROB 1 ///<probability of performing addition, substitution or deletion of word in rule's successor
+#define FL_MUTGROUPSCOUNT 2 ///<count of possible groups for mutations
+//@}
+
+/** @name Codes for probabilities of mutating word names or formula during change */
+//@{
+#define FL_CHG_WORD_FORMULA 0 ///<probability of changing formula of one of parameters
+#define FL_CHG_WORD_NAME 1 ///<probability of changing word name to other word name with <= number of parameters
+#define FL_CHG_COUNT 2 ///<count of possible changes in words
+//@}
+
+/** @name Codes for probabilities of choosing one of word types for addition */
+//@{
+#define FL_ADD_STICK  0 ///<probability of adding stick
+#define FL_ADD_NEURO  1 ///<probability of adding neuron
+#define FL_ADD_CONN   2 ///<probability of adding connection
+#define FL_ADD_ROT    3 ///<probability of adding one of rotation words
+#define FL_ADD_OTHER  4 ///<probability of adding word defined in the genotype
+#define FL_ADD_BRANCH 5 ///<probability of adding a branch
+#define FL_ADD_COUNT  6 ///<count of possible additions
+//@}
 
 class Geno_fL : public GenoOperators
 {
+private:
+
+	/**
+	 * Adds word with a given definition to the list in place pointed by an iterator.
+	 * @param list list, to which new word will be added
+	 * @param definition temporal object that will act as pattern for newly created word
+	 * @param it the iterator pointing to addition point
+	 * @return true
+	 */
+	bool addWord(std::list<fL_Word *>* list, fL_Word *definition, std::list<fL_Word *>::iterator it);
+
+	/**
+	 * Selects axiom or one of rule's successors.
+	 * @param creature the object with defined axiom and rules
+	 * @param numparams reference holding the number of parameters that are available for this list, 0 for axiom
+	 * @param ruleid the index of the rule in rules structure or -1 if selected sequence is the axiom
+	 * @return pointer to a selected sequence
+	 */
+	std::list<fL_Word *>* selectRandomSequence(fL_Builder *creature, int &numparams, int &ruleid);
+
+	/**
+	 * Selects word definition according to a method. Method is one of
+	 * values FL_ADD_STICK, FL_ADD_NEURO, FL_ADD_CONN, FL_ADD_ROT, FL_ADD_OTHER
+	 * etc. If FL_ADD_OTHER is chosen, then one of defined words is chosen. If
+	 * there are no defined words, then one of built-in words is chosen.
+	 * If FL_ADD_BRANCH is selected, then method returns NULL.
+	 * @param creature current genotype
+	 * @param method one of methods of addition
+	 * @return object defining one of genotype words, or NULL if branching method is used
+	 */
+	fL_Word* randomWordDefinition(fL_Builder *creature, int method);
+
+	/**
+	 * Tries to find appropriate word in second creature that matches the word in first creature.
+	 * Firstly, it tries to check if some word is not already assigned to a word in second creature.
+	 * If the searching is successful, then appropriate word is used. Otherwise, method tries to find
+	 * word that matches by name and number of parameters, or at least by number of parameters and hasn't
+	 * been used already for other translation. If this is impossible, method creates new word definition
+	 * for the second creature.
+	 * @param from creature, from rule is taken
+	 * @param to creature, which takes the rule
+	 * @param fromword word from the first creature that needs to be translated
+	 * @param map hashmap for current assignments
+	 * @return word instance that need to be used by generated rule for the second genotype
+	 */
+	fL_Word* getAppropriateWord(fL_Builder *from, fL_Builder *to, fL_Word *fromword, std::unordered_map<std::string, std::string> &map);
+
+	/**
+	 * Migrates random rule from one creature to the other creature.
+	 * @param from creature, from rule is taken
+	 * @param to creature, which takes the rule
+	 * @param numselrules number of rules that need to be moved
+	 */
+	void migrateRandomRules(fL_Builder *from, fL_Builder *to, int numselrules);
+
+	/**
+	 * Deletes branch from a given sequence starting from iterator. The deletion
+	 * removes only braces, not whole branch.
+	 * @param list current list that needs to be modified
+	 * @param openbranchposition the iterator pointing to the open branch word
+	 */
+	void deleteBranch(std::list<fL_Word *> *list, std::list<fL_Word *>::iterator openbranchposition);
 public:
+	double operations[FL_OPCOUNT]; ///<Relative probabilities of mutation types
+	double groupprobabilities[FL_MUTGROUPSCOUNT]; ///<Relative probabilities of changing elements in rules or axioms
+	double chgoperations[FL_CHG_COUNT]; ///<Relative probabilities of changing word names or parameters of word during change mutation
+	double addtypes[FL_ADD_COUNT]; ///<Relative probabilities of selecting special word types
+
+	double iterchangestep; ///<minimal value, by which time of development is modified
+	int maxdefinedwords; ///<maximal number of defined words for single fL genotype
+
 	Geno_fL();
 
-	//	int checkValidity(const char *geno, const char *genoname);
+	int checkValidity(const char *geno, const char *genoname);
 
-	//	int validate(char *&geno, const char *genoname);
+	int validate(char *&geno, const char *genoname);
 
-	//	int mutate(char *&geno, float& chg, int &method);
+	int mutate(char *&geno, float& chg, int &method);
 
-	//	int crossOver(char *&g1, char *&g2, float& chg1, float& chg2);
+	int crossOver(char *&g1, char *&g2, float& chg1, float& chg2);
 
-	//	virtual const char* getSimplest() { return "5\naaazz"; }
+	const char* getSimplest() { return "i:axiom=\"S()\", time=0, maxwords=300"; }
 
-	//	uint32_t style(const char *geno, int pos);
+	uint32_t style(const char *geno, int pos);
 };
 
-#endif // _FL_OPER_
+#endif // _FL_OPER_H_
