Add evaluator
diff --git a/.gitignore b/.gitignore
index 82ffdc1..e37d10e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
cmake-build-*
*.user
build
+.cache
diff --git a/Evaluator.cpp b/Evaluator.cpp
index 70ed91b..e4e5d6f 100644
--- a/Evaluator.cpp
+++ b/Evaluator.cpp
@@ -1,2 +1,138 @@
#include "Evaluator.h"
+#include "Function.h"
+#include "Matcher.h"
+#include "VarContext.h"
+#include <QDebug>
+
+RuntimeResult::RuntimeResult(QList<Token> result)
+{
+ _result = result;
+}
+
+RuntimeResult::RuntimeResult(QString message)
+{
+ _errorMessage = message;
+}
+
+bool RuntimeResult::success() const
+{
+ return _success;
+}
+
+QString RuntimeResult::message() const
+{
+ return _errorMessage;
+}
+
+QList<Token> RuntimeResult::result() const
+{
+ return _result;
+}
+
+RuntimeResult::operator QString() const
+{
+ return QString(_success) + " " + _errorMessage;
+}
+
+void Evaluator::addFunction(Function func)
+{
+ _functions[func.name()] = func;
+}
+
+void Evaluator::clearFunction(QString name)
+{
+ _functions.remove(name);
+}
+
+RuntimeResult Evaluator::evaluate(AstNode node, VarContext ctx)
+{
+ if (node.isSym())
+ {
+ return RuntimeResult(QList<Token>{Token(node.symbol())});
+ }
+ else if (node.isIdent())
+ {
+ return RuntimeResult(QList<Token>{Token(node.name())});
+ }
+ else if (node.isVar())
+ {
+ if (!ctx.exists(node.name()) || ctx.exists(node.name()) != node.symbol())
+ return RuntimeResult("Variable " + node + " is not defined");
+
+ if (node.symbol() == 'e')
+ {
+ return RuntimeResult(ctx.expressionVar(node.name()));
+ }
+ else
+ {
+ return RuntimeResult(QList<Token>{
+ ctx.singleVar(node.name())
+ });
+ }
+ }
+ else if (node.isParen())
+ {
+ QList<Token> result;
+
+ for (const AstNode &n : node.parenContent())
+ {
+ RuntimeResult internalResult = evaluate(n, ctx);
+ if (!internalResult.success())
+ return internalResult;
+
+ result.append(internalResult.result());
+ }
+
+ return RuntimeResult(QList<Token>{
+ Token(result)
+ });
+ }
+ else if (node.isFunc())
+ {
+ QList<Token> args;
+
+ for (const AstNode &arg : node.funcArgs())
+ {
+ RuntimeResult internalResult = evaluate(arg, ctx);
+ if (!internalResult.success())
+ return internalResult;
+
+ args.append(internalResult.result());
+ }
+
+ return callFunction(node.name(), args);
+ }
+
+ return RuntimeResult("#TYPE_ERROR");
+}
+
+RuntimeResult Evaluator::callFunction(QString name, QList<Token> args)
+{
+ if (!_functions.contains(name))
+ return RuntimeResult("Function " + name + " is not defined.");
+
+ Function func = _functions[name];
+
+ for (const Sentence &sentence : func.sentences())
+ {
+ MatchResult res = match(args, sentence.pattern(), VarContext());
+
+ if (!res.success)
+ continue;
+
+ QList<Token> final;
+ for (const AstNode &node : sentence.result())
+ {
+ RuntimeResult argRes = evaluate(node, res.context);
+ if (!argRes.success())
+ return argRes;
+
+ final.append(argRes.result());
+ }
+
+ return RuntimeResult(final);
+ }
+
+ return RuntimeResult("Function " + name + " had no matching sentences for input");
+}
diff --git a/Evaluator.h b/Evaluator.h
index 3eaef52..62fd23f 100644
--- a/Evaluator.h
+++ b/Evaluator.h
@@ -1,5 +1,39 @@
#pragma once
+#include <QMap>
+
+#include "Token.h"
+#include "AstNode.h"
+#include "Function.h"
+#include "VarContext.h"
+
+class RuntimeResult
+{
+public:
+ RuntimeResult(QList<Token> result);
+ RuntimeResult(QString message);
+
+ bool success() const;
+ QString message() const;
+ QList<Token> result() const;
+
+ operator QString() const;
+
+private:
+ bool _success = true;
+ QString _errorMessage;
+ QList<Token> _result;
+};
+
class Evaluator {
public:
+ Evaluator() = default;
+
+ void addFunction(Function func);
+ void clearFunction(QString name);
+ RuntimeResult evaluate(AstNode node, VarContext ctx);
+ RuntimeResult callFunction(QString name, QList<Token> args);
+
+private:
+ QMap<QString, Function> _functions;
};
diff --git a/Matcher.cpp b/Matcher.cpp
index 42fd155..460253a 100644
--- a/Matcher.cpp
+++ b/Matcher.cpp
@@ -9,7 +9,7 @@
return MatchResult{true, context};
}
- if (pattern.empty() && !data.empty())
+ if ((pattern.empty() && !data.empty()) || (data.empty() && !pattern.empty()))
{
return MatchResult{false, context};
}
@@ -105,7 +105,8 @@
case 'e':
// Now this is tricky
- for (int matchSyms = 1; matchSyms < data.length(); matchSyms++)
+ // TODO: Optimize this to check if there is an obvious length that this expression has to be
+ for (int matchSyms = 1; matchSyms <= data.length(); matchSyms++)
{
QList<Token> slice = listSlice(data, 0, matchSyms);
VarContext newContext = context;
diff --git a/Matcher.h b/Matcher.h
index 24fc795..c417191 100644
--- a/Matcher.h
+++ b/Matcher.h
@@ -38,6 +38,9 @@
{
QList<T> prime;
+ if (from >= list.length())
+ return prime;
+
// I guess we'll just panic if it's too long
// TODO: ERROR HANDLING
for (int i = 0; i < to - from; i++)
diff --git a/Token.cpp b/Token.cpp
index 56c32a9..ac104a4 100644
--- a/Token.cpp
+++ b/Token.cpp
@@ -1,3 +1,153 @@
#include "Token.h"
+#include "AstNode.h"
-// Nothing, aparently
+template class TokenBase<Token>;
+
+// This is kind of ugly and breaks separation of concerns; but if I don't do
+// this I have to define everything in headers which is worse.
+template class TokenBase<AstNode>;
+
+template <typename T>
+TokenBase<T>::TokenBase(const T &other)
+{
+ _type = other._type;
+ _stringVal = other._stringVal;
+ _listVal = other._listVal;
+ _charVal = other._charVal;
+}
+
+template <typename T>
+TokenBase<T>::TokenBase(QChar symbol)
+{
+ _type = SYM;
+ _charVal = symbol;
+}
+
+template <typename T>
+TokenBase<T>::TokenBase(QString identifier)
+{
+ _type = IDENT;
+ _stringVal = identifier;
+}
+
+template <typename T>
+TokenBase<T>::TokenBase(QList<T> parenthesized)
+{
+ _type = PAREN;
+ _listVal = std::move(parenthesized);
+}
+
+template <typename T>
+TokenBase<T>::TokenBase(char varType, const QString name)
+{
+ _type = VAR;
+ _charVal = varType;
+ _stringVal = name;
+}
+
+template <typename T>
+bool TokenBase<T>::isSym() const
+{
+ return _type == SYM;
+}
+
+template <typename T>
+bool TokenBase<T>::isIdent() const
+{
+ return _type == IDENT;
+}
+
+template <typename T>
+bool TokenBase<T>::isParen() const
+{
+ return _type == PAREN;
+}
+template <typename T>
+bool TokenBase<T>::isVar() const
+{
+ return _type == VAR;
+}
+
+template <typename T>
+TokenBase<T>::TokenBase() : TokenBase("Null")
+{
+}
+
+template <typename T>
+bool TokenBase<T>::operator==(const T &other) const
+{
+ return _type == other._type && _stringVal == other._stringVal && _charVal == other._charVal && _listVal == other._listVal;
+}
+
+template <typename T>
+QList<T> TokenBase<T>::parenContent()
+{
+ if (isParen())
+ {
+ return _listVal;
+ }
+ else
+ {
+ return {};
+ }
+}
+
+template <typename T>
+char TokenBase<T>::varType() const
+{
+ return _charVal.toLatin1();
+}
+
+template <typename T>
+const QString &TokenBase<T>::name() const
+{
+ return _stringVal;
+}
+
+template <typename T>
+bool TokenBase<T>::operator!=(const T &other) const
+{
+ return !(this->operator==(other));
+}
+
+template <typename T>
+TokenBase<T>::operator QString() const
+{
+ if (isIdent())
+ return _stringVal;
+ if (isSym())
+ return _charVal;
+ if (isVar())
+ return QString(_charVal) + "." + _stringVal;
+ if (isParen())
+ {
+ QStringList parts;
+ for (const T &tok : _listVal)
+ {
+ parts.append(static_cast<QString>(tok));
+ }
+
+ return "(" + parts.join(" ") + ")";
+ }
+
+ return "Null";
+}
+
+template <typename T>
+int TokenBase<T>::type() const
+{
+ return _type;
+}
+
+template <typename T>
+QString TokenBase<T>::typeToString(int type)
+{
+ static const QString typeNames[] = {"SYMBOL", "IDENT", "PAREN", "VAR"};
+ return typeNames[type];
+}
+
+template <typename T>
+QChar TokenBase<T>::symbol() const
+{
+ return _charVal;
+}
diff --git a/Token.h b/Token.h
index dfa0223..75c4455 100644
--- a/Token.h
+++ b/Token.h
@@ -58,148 +58,3 @@
};
using LTok = QList<Token>;
-
-template <typename T>
-TokenBase<T>::TokenBase(const T &other)
-{
- _type = other._type;
- _stringVal = other._stringVal;
- _listVal = other._listVal;
- _charVal = other._charVal;
-}
-
-template <typename T>
-TokenBase<T>::TokenBase(QChar symbol)
-{
- _type = SYM;
- _charVal = symbol;
-}
-
-template <typename T>
-TokenBase<T>::TokenBase(QString identifier)
-{
- _type = IDENT;
- _stringVal = identifier;
-}
-
-template <typename T>
-TokenBase<T>::TokenBase(QList<T> parenthesized)
-{
- _type = PAREN;
- _listVal = std::move(parenthesized);
-}
-
-template <typename T>
-TokenBase<T>::TokenBase(char varType, const QString name)
-{
- _type = VAR;
- _charVal = varType;
- _stringVal = name;
-}
-
-template <typename T>
-bool TokenBase<T>::isSym() const
-{
- return _type == SYM;
-}
-
-template <typename T>
-bool TokenBase<T>::isIdent() const
-{
- return _type == IDENT;
-}
-
-template <typename T>
-bool TokenBase<T>::isParen() const
-{
- return _type == PAREN;
-}
-template <typename T>
-bool TokenBase<T>::isVar() const
-{
- return _type == VAR;
-}
-
-template <typename T>
-TokenBase<T>::TokenBase() : TokenBase("Null")
-{
-}
-
-template <typename T>
-bool TokenBase<T>::operator==(const T &other) const
-{
- return _type == other._type && _stringVal == other._stringVal && _charVal == other._charVal && _listVal == other._listVal;
-}
-
-template <typename T>
-QList<T> TokenBase<T>::parenContent()
-{
- if (isParen())
- {
- return _listVal;
- }
- else
- {
- return {};
- }
-}
-
-template <typename T>
-char TokenBase<T>::varType() const
-{
- return _charVal.toLatin1();
-}
-
-template <typename T>
-const QString &TokenBase<T>::name() const
-{
- return _stringVal;
-}
-
-template <typename T>
-bool TokenBase<T>::operator!=(const T &other) const
-{
- return !(this->operator==(other));
-}
-
-template <typename T>
-TokenBase<T>::operator QString() const
-{
- if (isIdent())
- return _stringVal;
- if (isSym())
- return _charVal;
- if (isVar())
- return QString(_charVal) + "." + _stringVal;
- if (isParen())
- {
- QStringList parts;
- for (const T &tok : _listVal)
- {
- parts.append(static_cast<QString>(tok));
- }
-
- return "(" + parts.join(" ") + ")";
- }
-
- return "Null";
-}
-
-template <typename T>
-int TokenBase<T>::type() const
-{
- return _type;
-}
-
-template <typename T>
-QString TokenBase<T>::typeToString(int type)
-{
- static const QString typeNames[] = {"SYMBOL", "IDENT", "PAREN", "VAR"};
- return typeNames[type];
-}
-
-template <typename T>
-QChar TokenBase<T>::symbol() const
-{
- return _charVal;
-}
diff --git a/main.cpp b/main.cpp
index 25c4504..2d40231 100644
--- a/main.cpp
+++ b/main.cpp
@@ -5,9 +5,73 @@
#include "Token.h"
#include "AstNode.h"
#include "Parser.h"
+#include "Evaluator.h"
+#include "VarContext.h"
int g_numFailed = 0;
+void testEval(QString function, QString expression, QString expected)
+{
+ Evaluator eval;
+ Parser funcParser(function),
+ exprParser(expression),
+ resParser(expected);
+
+ Function func;
+
+ QList<AstNode> expr = exprParser.parseMany<AstNode>();
+ QList<Token> res = resParser.parseMany<Token>();
+
+ QList<Token> result;
+
+ exprParser.skip();
+ resParser.skip();
+ while (funcParser.parseFunctionDefinition(&func))
+ {
+ eval.addFunction(func);
+ }
+
+ funcParser.skip();
+
+ if (!exprParser.atEnd() || !resParser.atEnd() || !funcParser.atEnd())
+ {
+ g_numFailed++;
+ qDebug() << "\n\033[31mTEST FAILS:\033[0m";
+ qDebug() << "Failed to fully parse expression, function or result";
+ qDebug() << function << expression << expected;
+
+ goto end;
+ }
+
+ for (const AstNode &node : expr)
+ {
+ RuntimeResult rr = eval.evaluate(node, VarContext());
+
+ if (!rr.success())
+ {
+ g_numFailed++;
+ qDebug() << "\n\033[31mTEST FAILS:\033[0m";
+ qDebug() << "Runtime error while evaluating" << node;
+ qDebug() << rr;
+
+ goto end;
+ }
+
+ result.append(rr.result());
+ }
+
+ if (result != res)
+ {
+ g_numFailed++;
+ qDebug() << "\n\033[31mTEST FAILS:\033[0m";
+ qDebug() << "Expected result" << res;
+ qDebug() << "Got" << result;
+ }
+
+end:
+ qDebug() << "\033[36mEvaluate\033[0m" << function << expression << "->" << result;
+}
+
void testMatch(const QString &test, bool shouldBe, const MatchResult &result)
{
if (result.success != shouldBe)
@@ -30,7 +94,8 @@
Parser dataParser(data),
patternParser(pattern);
- testMatch(pattern + " = " + data, shouldBe, match(dataParser.parseMany<Token>(), patternParser.parseMany<Token>(), VarContext()));
+ testMatch(pattern + " = " + data, shouldBe,
+ match(dataParser.parseMany<Token>(), patternParser.parseMany<Token>(), VarContext()));
}
void testParseAst(QString string)
@@ -114,6 +179,7 @@
// testMatch("(y)f Middle-stuff y", "(s.a) e.Middle s.a");
testMatch("(a)", "(a)");
+ testMatch("hello", "s.A e.Rest");
}
void testAllParses()
@@ -135,7 +201,12 @@
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>; } ");
+ testParseFunc("Palindrome { = T; s.A = T; s.A s.A = T; s.A e.Middle s.A = <Palindrome e.Middle>; e.Ignored = F; } ");
+}
+
+void testAllEvals()
+{
+ testEval("First {s.A e.Rest = s.A;}", "<First hello>", "h");
}
int main(int argc, char *argv[])
@@ -147,6 +218,8 @@
testAllParses();
qDebug() << "";
testAllFunctionDefs();
+ qDebug() << "";
+ testAllEvals();
qDebug() << "";
return testResults();