Add function parser
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8274f8f..ac5dbfb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -11,7 +11,7 @@
         Core
         REQUIRED)
 
-add_executable(REFAL main.cpp Token.cpp Token.h Matcher.cpp Matcher.h VarContext.cpp VarContext.h Parser.cpp Parser.h AstNode.cpp AstNode.h Evaluator.cpp Evaluator.h)
+add_executable(REFAL main.cpp Token.cpp Token.h Matcher.cpp Matcher.h VarContext.cpp VarContext.h Parser.cpp Parser.h AstNode.cpp AstNode.h Evaluator.cpp Evaluator.h Function.cpp Function.h)
 target_link_libraries(REFAL
         Qt::Core
         )
diff --git a/Function.cpp b/Function.cpp
new file mode 100644
index 0000000..6974fa6
--- /dev/null
+++ b/Function.cpp
@@ -0,0 +1,84 @@
+#include "Function.h"
+
+template <typename T>
+QString join(QList<T> list, QString sep)
+{
+	QStringList strings;
+
+	for (const T &item : list)
+	{
+		strings.append(static_cast<QString>(item));
+	}
+
+	return strings.join(sep);
+}
+
+Sentence::Sentence(QList<Token> pattern, QList<AstNode> result)
+{
+	_pattern = pattern;
+	_result = result;
+}
+
+QList<Token> Sentence::pattern() const
+{
+	return _pattern;
+}
+
+QList<AstNode> Sentence::result() const
+{
+	return _result;
+}
+
+Sentence::operator QString() const
+{
+	return join(_pattern, " ") + " = " + join(_result, " ") + ";";
+}
+
+Function::Function(QString name)
+	: Function(name, {})
+{
+}
+
+Function::Function(QString name, QList<Sentence> sentences)
+{
+	_name = name;
+	_sentences = sentences;
+}
+
+void Function::addSentence(Sentence sentence)
+{
+	_sentences.append(sentence);
+}
+
+
+QString Function::name() const
+{
+	return _name;
+}
+
+QList<Sentence> Function::sentences() const
+{
+	return _sentences;
+}
+
+Function::operator QString() const
+{
+	QString buffer = name() + " { ";
+	int leftPadding = buffer.length();
+
+	QString spaces;
+	for (int i = 0; i < leftPadding; i++)
+		spaces += " ";
+
+	for (int i = 0; i < _sentences.length(); i++)
+	{
+		if (i)
+			buffer += "\n" + spaces;
+
+		buffer += static_cast<QString>(_sentences[i]);
+	}
+
+	buffer += " }";
+
+	return buffer;
+}
diff --git a/Function.h b/Function.h
new file mode 100644
index 0000000..cb3ba1c
--- /dev/null
+++ b/Function.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "Token.h"
+#include "AstNode.h"
+
+class Sentence {
+public:
+	Sentence() = default;
+	Sentence(QList<Token> pattern, QList<AstNode> result);
+
+	QList<Token> pattern() const;
+	QList<AstNode> result() const;
+
+	operator QString() const;
+
+protected:
+	QList<Token> _pattern;
+	QList<AstNode> _result;
+};
+
+class Function {
+public:
+	Function() = default;
+	explicit Function(QString name);
+	Function(QString name, QList<Sentence> sentences);
+
+	void addSentence(Sentence sentence);
+
+	QString name() const;
+	QList<Sentence> sentences() const;
+
+	operator QString() const;
+
+private:
+	QString _name;
+	QList<Sentence> _sentences;
+};
diff --git a/Parser.cpp b/Parser.cpp
index 9233fbd..fd07c79 100644
--- a/Parser.cpp
+++ b/Parser.cpp
@@ -213,3 +213,70 @@
            parseSymbol(node) ||
            parseParens(node);
 }
+
+bool Parser::parseSentence(Sentence *sentence)
+{
+    int pos = _pos;
+
+    QList<Token> pattern = parseMany<Token>();
+
+    skip();
+
+    if (get() != '=')
+    {
+        _pos = pos;
+        return false;
+    }
+
+    QList<AstNode> result = parseMany<AstNode>();
+
+    skip();
+
+    if (get() != ';')
+    {
+        _pos = pos;
+        return false;
+    }
+
+    *sentence = Sentence(pattern, result);
+    return true;
+}
+
+bool Parser::parseFunctionDefinition(Function *function)
+{
+    int pos = _pos;
+
+    Token identifier;
+    if (!parseIdentifier(&identifier))
+    {
+        _pos = pos;
+        return false;
+    }
+
+    QString name = identifier.name();
+    Function func(name);
+
+    skip();
+
+    if (get() != '{')
+    {
+        _pos = pos;
+        return false;
+    }
+
+    Sentence sentence;
+    while (parseSentence(&sentence))
+    {
+        func.addSentence(sentence);
+        skip();
+    }
+
+    if (get() != '}')
+    {
+        _pos = pos;
+        return false;
+    }
+
+    *function = func;
+    return true;
+}
diff --git a/Parser.h b/Parser.h
index fb56701..1426989 100644
--- a/Parser.h
+++ b/Parser.h
@@ -4,6 +4,7 @@
 
 #include "Token.h"
 #include "AstNode.h"
+#include "Function.h"
 
 class Parser
 {
@@ -39,6 +40,9 @@
     template <typename T>
     bool parseOne(T *node);
 
+    bool parseSentence(Sentence *sentence);
+    bool parseFunctionDefinition(Function *function);
+
 private:
     int _pos = 0;
     QString _input;
diff --git a/main.cpp b/main.cpp
index 8ef1fb4..25c4504 100644
--- a/main.cpp
+++ b/main.cpp
@@ -42,6 +42,25 @@
     qDebug() << "\033[36mParse\033[0m" << string << result;
 }
 
+void testParseFunc(QString string)
+{
+    Parser parser{string};
+
+    Function func;
+
+    if (!parser.parseFunctionDefinition(&func))
+    {
+        g_numFailed++;
+        qDebug() << "\n\033[31mTEST FAILS:\033[0m";
+        qDebug() << string;
+    }
+    else
+    {
+        qDebug() << "\033[36mFunction\033[0m";
+        qDebug().noquote() << func;
+    }
+}
+
 int testResults()
 {
     if (g_numFailed == 0)
@@ -109,6 +128,14 @@
     testParseAst("(<Prout hi>)");
     testParseAst("<If T Then (<Prout hi>) Else (<Prout sorry>)>");
     testParseAst("(s.a) e.Middle s.a");
+    testParseAst("Hello; Goodbye");
+    testParseAst("Key = Value");
+}
+
+void testAllFunctionDefs()
+{
+    testParseFunc("Test { = HI; }");
+    testParseFunc("Palindrome { = T; s.A = T; s.A s.A = T; s.A e.Middle s.A = <Palindrome e.Middle>; } ");
 }
 
 int main(int argc, char *argv[])
@@ -118,6 +145,8 @@
     testAllMatches();
     qDebug() << "";
     testAllParses();
+    qDebug() << "";
+    testAllFunctionDefs();
 
     qDebug() << "";
     return testResults();