Add REPL
diff --git a/.gitignore b/.gitignore
index e37d10e..2892543 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@
*.user
build
.cache
+compile_commands.json
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ac5dbfb..b0be929 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,19 +1,17 @@
-cmake_minimum_required(VERSION 3.20)
+cmake_minimum_required(VERSION 3.15)
project(REFAL)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+find_package(Qt5 COMPONENTS Core REQUIRED)
-find_package(Qt5 COMPONENTS
- 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 Function.cpp Function.h Repl.cpp
+ Repl.h PPrint.cpp PPrint.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
- )
-
-add_compile_options("-fsanitize=address")
+target_link_libraries(REFAL Qt::Core readline)
diff --git a/Evaluator.cpp b/Evaluator.cpp
index e4e5d6f..fd46805 100644
--- a/Evaluator.cpp
+++ b/Evaluator.cpp
@@ -13,6 +13,7 @@
RuntimeResult::RuntimeResult(QString message)
{
_errorMessage = message;
+ _success = false;
}
bool RuntimeResult::success() const
diff --git a/Matcher.cpp b/Matcher.cpp
index 460253a..1b65158 100644
--- a/Matcher.cpp
+++ b/Matcher.cpp
@@ -104,20 +104,23 @@
return match(data, pattern, context);
case 'e':
- // Now this is tricky
- // 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++)
+ // Now this is tricky TODO: Optimize this to check if
+ // there is an obvious length that this expression has
+ // to be
+ for (int matchSyms = 0; matchSyms <= data.length(); matchSyms++)
{
QList<Token> slice = listSlice(data, 0, matchSyms);
VarContext newContext = context;
newContext.add(ph.varType(), ph.name(), slice);
- MatchResult tryMatch = match(listSlice(data, matchSyms, data.length()), pattern, newContext);
+ MatchResult tryMatch = match(
+ listSlice(data, matchSyms, data.length()),
+ pattern, newContext);
+
if (tryMatch.success)
{
return tryMatch;
}
- // else matchSyms ++
}
// If this worked we would have returned already
return MatchResult{false, context};
@@ -129,7 +132,7 @@
}
}
- qDebug() << "FALLING THROUGH, THIS SHOULD NOT HAPPEN";
+ qFatal("FALLING THROUGH, THIS SHOULD NOT HAPPEN");
// Fallthrough
return MatchResult{false, context};
}
diff --git a/PPrint.cpp b/PPrint.cpp
new file mode 100644
index 0000000..a072a1b
--- /dev/null
+++ b/PPrint.cpp
@@ -0,0 +1 @@
+#include "PPrint.h"
diff --git a/PPrint.h b/PPrint.h
new file mode 100644
index 0000000..3a00379
--- /dev/null
+++ b/PPrint.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <QString>
+#include <QList>
+
+#include "Token.h"
+#include "AstNode.h"
+
+template <typename T>
+QString pprint(T val);
+
+template <>
+QString pprint<Token>(Token val);
+
+template <>
+QString pprint<AstNode>(AstNode val);
+
+template <typename T>
+QString pprint(QList<T> val)
+{
+ QStringList out;
+
+ for (const T &v : val)
+ out.append(static_cast<QString>(v));
+
+ return out.join(" ");
+}
+
+template <typename T>
+QString pprint(T val)
+{
+ return static_cast<QString>(val);
+}
diff --git a/Repl.cpp b/Repl.cpp
new file mode 100644
index 0000000..415602f
--- /dev/null
+++ b/Repl.cpp
@@ -0,0 +1,129 @@
+#include <stdio.h>
+#include <string.h>
+
+#include <QDebug>
+
+#include "Repl.h"
+#include "Parser.h"
+#include "PPrint.h"
+
+// JANK! librl isn't namespaced!
+namespace ReadLine
+{
+#include <readline/readline.h>
+#include <readline/history.h>
+}
+
+
+Repl::Repl()
+{
+}
+
+char *Repl::prompt()
+{
+ static char p[] = "\033[36mREFAL >\033[0m ";
+ return p;
+}
+
+QString Repl::readLine()
+{
+ char *line = ReadLine::readline(prompt());
+
+ if (!line)
+ {
+ _running = false;
+ return "";
+ }
+
+ ReadLine::add_history(line);
+
+ QString string = QString::fromUtf8(line);
+
+ free(line);
+
+ return string;
+}
+
+void Repl::start()
+{
+ while (_running)
+ {
+ QString line = readLine();
+
+ line = line.trimmed();
+
+ QList<AstNode> expr;
+
+ if (trySpecialCase(line))
+ {}
+ else if (tryEvaluate(line, &expr))
+ {
+ bool okay = true;
+ QList<Token> out;
+
+ for (const AstNode &node : expr)
+ {
+ RuntimeResult res = _eval.evaluate(node, VarContext());
+
+ if (res.success())
+ {
+ out.append(res.result());
+ }
+ else
+ {
+ qDebug() << "Failed to evaluate" << node;
+ qDebug() << res.message();
+ okay = false;
+ break;
+ }
+ }
+
+ if (okay)
+ {
+ qDebug() << pprint(out);
+ }
+ }
+ else
+ {
+ qDebug() << "What?";
+ }
+ }
+}
+
+bool Repl::trySpecialCase(QString line)
+{
+ if (line.startsWith("."))
+ {
+ if (line == ".q" || line == ".quit")
+ {
+ _running = false;
+ }
+ else
+ {
+ qDebug().noquote() << "Unknown special command, try .help";
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+bool Repl::tryEvaluate(QString line, QList<AstNode> *expr)
+{
+ Parser parser(line);
+ Function func;
+
+ if (parser.parseFunctionDefinition(&func))
+ {
+ _eval.addFunction(func);
+ *expr = {};
+
+ return true;
+ }
+
+ *expr = parser.parseMany<AstNode>();
+ parser.skip();
+
+ return parser.atEnd();
+}
diff --git a/Repl.h b/Repl.h
new file mode 100644
index 0000000..a4939d3
--- /dev/null
+++ b/Repl.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <QString>
+#include <QList>
+
+#include "Evaluator.h"
+#include "AstNode.h"
+
+class Repl
+{
+public:
+ Repl();
+
+ void start();
+ char *prompt();
+
+protected:
+ QString readLine();
+ bool trySpecialCase(QString line);
+ bool tryEvaluate(QString line, QList<AstNode> *expr);
+
+ Evaluator _eval;
+
+ bool _running = true;
+};
diff --git a/main.cpp b/main.cpp
index 2d40231..765e380 100644
--- a/main.cpp
+++ b/main.cpp
@@ -1,4 +1,5 @@
#include <QCoreApplication>
+#include <QCommandLineParser>
#include <QDebug>
#include "Matcher.h"
@@ -7,6 +8,7 @@
#include "Parser.h"
#include "Evaluator.h"
#include "VarContext.h"
+#include "Repl.h"
int g_numFailed = 0;
@@ -201,7 +203,7 @@
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>; e.Ignored = F; } ");
+ testParseFunc("Palindrome { = T; s.A = T; s.A e.Middle s.A = <Palindrome e.Middle>; e.Ignored = F; } ");
}
void testAllEvals()
@@ -212,15 +214,36 @@
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
+ QCoreApplication::setApplicationName("REFAL");
+ QCoreApplication::setApplicationVersion("1.0-a1");
- testAllMatches();
- qDebug() << "";
- testAllParses();
- qDebug() << "";
- testAllFunctionDefs();
- qDebug() << "";
- testAllEvals();
+ QCommandLineParser cli;
+ cli.setApplicationDescription("REFAL interpreter");
+ cli.addHelpOption();
+ cli.addVersionOption();
- qDebug() << "";
- return testResults();
+ QCommandLineOption testOption(QStringList{"t", "test"}, "Run test suite");
+ cli.addOption(testOption);
+
+ cli.process(a);
+
+ if (cli.isSet(testOption))
+ {
+ testAllMatches();
+ qDebug() << "";
+ testAllParses();
+ qDebug() << "";
+ testAllFunctionDefs();
+ qDebug() << "";
+ testAllEvals();
+
+ qDebug() << "";
+
+ return testResults();
+ }
+ else
+ {
+ Repl repl;
+ repl.start();
+ }
}
diff --git a/notes.txt b/notes.txt
index f9d7a80..fe6f185 100644
--- a/notes.txt
+++ b/notes.txt
@@ -1,4 +1,12 @@
-export QT_LOGGING_RULES="*.debug=true"
+SETUP
+
+ If you don't get console output from REFAL run:
+ export QT_LOGGING_RULES="*.debug=true"
+
+
+DEPENDENCIES
+
+ Install libedit-dev and libqt5core5a
OPTIMIZATION POSSIBILITIES:
@@ -14,4 +22,3 @@
- Pattern matching and delegation (which sentence of a function do we choose to evaluate)
- Expression evaluation (how do we expand an AstNode stream into a Token stream)
-
\ No newline at end of file