Fix test regression: incomplete errors no longer cause var parsing to fail
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b0be929..95aefef 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -12,6 +12,6 @@
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)
+ Repl.h PPrint.cpp PPrint.h StdLib.cpp StdLib.h)
target_link_libraries(REFAL Qt::Core readline)
diff --git a/Evaluator.cpp b/Evaluator.cpp
index 696e555..b52b843 100644
--- a/Evaluator.cpp
+++ b/Evaluator.cpp
@@ -36,6 +36,10 @@
return QString(_success) + " " + _errorMessage;
}
+Evaluator::Evaluator()
+{
+}
+
void Evaluator::addFunction(Function func)
{
_functions[func.name()] = func;
@@ -126,6 +130,11 @@
if (!res.success)
continue;
+ if (sentence.isExternal())
+ {
+ return RuntimeResult(sentence.externResult(args));
+ }
+
QList<Token> final;
for (const AstNode &node : sentence.result())
{
diff --git a/Evaluator.h b/Evaluator.h
index 62fd23f..5cc7851 100644
--- a/Evaluator.h
+++ b/Evaluator.h
@@ -27,7 +27,7 @@
class Evaluator {
public:
- Evaluator() = default;
+ Evaluator();
void addFunction(Function func);
void clearFunction(QString name);
diff --git a/Function.cpp b/Function.cpp
index 6974fa6..39ac957 100644
--- a/Function.cpp
+++ b/Function.cpp
@@ -1,4 +1,6 @@
#include "Function.h"
+#include "Parser.h"
+#include <QDebug>
template <typename T>
QString join(QList<T> list, QString sep)
@@ -13,12 +15,32 @@
return strings.join(sep);
}
+Sentence::~Sentence()
+{
+}
+
Sentence::Sentence(QList<Token> pattern, QList<AstNode> result)
{
_pattern = pattern;
_result = result;
}
+Sentence::Sentence(QList<Token> pattern, SentenceResultFn result)
+{
+ _pattern = pattern;
+ _native = result;
+}
+
+bool Sentence::isExternal() const
+{
+ return _native != nullptr;
+}
+
+QList<Token> Sentence::externResult(QList<Token> args) const
+{
+ return _native(std::move(args));
+}
+
QList<Token> Sentence::pattern() const
{
return _pattern;
@@ -50,6 +72,22 @@
_sentences.append(sentence);
}
+void Function::addNativeSentence(QString pattern, SentenceResultFn fn)
+{
+ Parser parser(std::move(pattern));
+ QList<Token> parsedPattern;
+
+ ParseResult res = parser.parseMany(&parsedPattern);
+ if (!res)
+ {
+ qDebug() << "Failed to parse pattern for native sentence";
+ qDebug() << res.message();
+ }
+ else
+ {
+ addSentence(Sentence(parsedPattern, std::move(fn)));
+ }
+}
QString Function::name() const
{
diff --git a/Function.h b/Function.h
index cb3ba1c..2cb862f 100644
--- a/Function.h
+++ b/Function.h
@@ -3,10 +3,17 @@
#include "Token.h"
#include "AstNode.h"
+using SentenceResultFn = std::function<QList<Token> (QList<Token>)>;
+
class Sentence {
public:
Sentence() = default;
+ ~Sentence();
Sentence(QList<Token> pattern, QList<AstNode> result);
+ Sentence(QList<Token> pattern, SentenceResultFn result);
+
+ bool isExternal() const;
+ QList<Token> externResult(QList<Token> args) const;
QList<Token> pattern() const;
QList<AstNode> result() const;
@@ -16,6 +23,7 @@
protected:
QList<Token> _pattern;
QList<AstNode> _result;
+ SentenceResultFn _native = nullptr;
};
class Function {
@@ -25,6 +33,7 @@
Function(QString name, QList<Sentence> sentences);
void addSentence(Sentence sentence);
+ void addNativeSentence(QString pattern, SentenceResultFn fn);
QString name() const;
QList<Sentence> sentences() const;
diff --git a/PPrint.cpp b/PPrint.cpp
index a072a1b..986c38c 100644
--- a/PPrint.cpp
+++ b/PPrint.cpp
@@ -1 +1,26 @@
#include "PPrint.h"
+
+#include <QTextStream>
+
+void sout(QString string)
+{
+ QTextStream(stdout) << string << '\n';
+}
+
+QString pprint(ParseResult val, const Parser &parser)
+{
+ if (val)
+ {
+ return "\033[32mOK\033[0m";
+ }
+
+ QString highlighted = parser.line(val.pos().line - 1) + "\n";
+
+ for (int i = 1; i < val.pos().lineOffset; i++)
+ {
+ highlighted += " ";
+ }
+ highlighted += "\033[31m^~~\033[0m";
+
+ return val.message() + " at " + val.pos() + "\n" + highlighted;
+}
diff --git a/PPrint.h b/PPrint.h
index 3a00379..5212334 100644
--- a/PPrint.h
+++ b/PPrint.h
@@ -5,6 +5,7 @@
#include "Token.h"
#include "AstNode.h"
+#include "Parser.h"
template <typename T>
QString pprint(T val);
@@ -31,3 +32,7 @@
{
return static_cast<QString>(val);
}
+
+QString pprint(ParseResult val, const Parser &parser);
+
+void sout(QString string);
diff --git a/Parser.cpp b/Parser.cpp
index 2c62f27..41f6727 100644
--- a/Parser.cpp
+++ b/Parser.cpp
@@ -13,7 +13,7 @@
ParseResult::ParseResult(int status, QString message, ParsePos pos)
{
_status = status;
- _message = message;
+ _message = message;
_pos = pos;
}
@@ -37,14 +37,6 @@
return _status;
}
-ParseResult ParseResult::operator ||(const ParseResult &other) const
-{
- if (_status == COMPLETE || _status == INCOMPLETE)
- return *this;
- else
- return other;
-}
-
Parser::Parser(QString input)
{
_input = input;
@@ -86,6 +78,11 @@
get();
}
+QString Parser::line(int n) const
+{
+ return _input.split("\n")[n];
+}
+
ParsePos Parser::save() const
{
return ParsePos{_line, _pos, _offset};
@@ -183,7 +180,7 @@
{
ParseResult ret(ParseResult::INCOMPLETE,
"Expected identifier or symbol after . in variable",
- pos);
+ save());
reset(pos);
@@ -262,8 +259,9 @@
AstNode head;
if (!(ret = parseIdentifier(&head)))
{
+ ParsePos endPos = save();
reset(pos);
- return ret;
+ return ParseResult(ParseResult::INCOMPLETE, "Expected identifier following < in function call", endPos);
}
QList<AstNode> body;
@@ -293,22 +291,35 @@
template <>
ParseResult Parser::parseOne<Token>(Token *node)
{
- return parseVariable(node) ||
- parseNumber(node) ||
- parseIdentifier(node) ||
- parseSymbol(node) ||
- parseParens(node);
+ ParseResult ret;
+
+ if ((ret = parseVariable(node)).status() == ParseResult::COMPLETE || ret.status() == ParseResult::INCOMPLETE)
+ return ret;
+ if ((ret = parseNumber(node)).status() == ParseResult::COMPLETE || ret.status() == ParseResult::INCOMPLETE)
+ return ret;
+ if ((ret = parseIdentifier(node)).status() == ParseResult::COMPLETE || ret.status() == ParseResult::INCOMPLETE)
+ return ret;
+ if ((ret = parseSymbol(node)).status() == ParseResult::COMPLETE || ret.status() == ParseResult::INCOMPLETE)
+ return ret;
+ return parseParens(node);
}
template <>
ParseResult Parser::parseOne<AstNode>(AstNode *node)
{
- return parseFunction(node) ||
- parseVariable(node) ||
- parseNumber(node) ||
- parseIdentifier(node) ||
- parseSymbol(node) ||
- parseParens(node);
+ ParseResult ret;
+
+ if ((ret = parseFunction(node)).status() == ParseResult::COMPLETE || ret.status() == ParseResult::INCOMPLETE)
+ return ret;
+ if ((ret = parseVariable(node)).status() == ParseResult::COMPLETE || ret.status() == ParseResult::INCOMPLETE)
+ return ret;
+ if ((ret = parseNumber(node)).status() == ParseResult::COMPLETE || ret.status() == ParseResult::INCOMPLETE)
+ return ret;
+ if ((ret = parseIdentifier(node)).status() == ParseResult::COMPLETE || ret.status() == ParseResult::INCOMPLETE)
+ return ret;
+ if ((ret = parseSymbol(node)).status() == ParseResult::COMPLETE || ret.status() == ParseResult::INCOMPLETE)
+ return ret;
+ return parseParens(node);
}
ParseResult Parser::parseSentence(Sentence *sentence)
@@ -391,7 +402,7 @@
skip();
}
- if (ret.status() == ParseResult::INCOMPLETE)
+ if (ret.status() == ParseResult::INCOMPLETE)
{
reset(pos);
return ret;
@@ -399,7 +410,7 @@
if (get() != '}')
{
- ret = ParseResult(ParseResult::INCOMPLETE, "Expected } at end of function");
+ ret = ParseResult(ParseResult::INCOMPLETE, "Expected } at end of function", save());
reset(pos);
return ret;
}
@@ -407,3 +418,8 @@
*function = func;
return true;
}
+
+ParsePos::operator QString()
+{
+ return QString::number(line) + ":" + QString::number(lineOffset);
+}
diff --git a/Parser.h b/Parser.h
index ee604f9..5d87a2b 100644
--- a/Parser.h
+++ b/Parser.h
@@ -11,6 +11,8 @@
int line = 0,
pos = 0,
lineOffset = 0;
+
+ operator QString();
};
class ParseResult
@@ -34,8 +36,6 @@
QString message() const;
int status() const;
- ParseResult operator ||(const ParseResult &other) const;
-
private:
int _status = COMPLETE;
QString _message = "";
@@ -53,6 +53,8 @@
void skip();
+ QString line(int n) const;
+
template <typename T>
ParseResult parseSymbol(T *node);
diff --git a/Repl.cpp b/Repl.cpp
index 037a758..cef5ed7 100644
--- a/Repl.cpp
+++ b/Repl.cpp
@@ -6,6 +6,7 @@
#include "Repl.h"
#include "Parser.h"
#include "PPrint.h"
+#include "StdLib.h"
// JANK! librl isn't namespaced!
namespace ReadLine
@@ -17,6 +18,7 @@
Repl::Repl()
{
+ StdLib().load(_eval);
}
char *Repl::prompt()
@@ -59,10 +61,11 @@
addHistory(line);
ParseResult ret;
+ Parser parser{line};
if (trySpecialCase(line))
{}
- else if ((ret = tryEvaluate(line, &expr)))
+ else if ((ret = tryEvaluate(parser, &expr)))
{
bool okay = true;
QList<Token> out;
@@ -86,12 +89,13 @@
if (okay)
{
- qDebug().noquote() << pprint(out);
+ sout(pprint(out));
}
}
else if (ret.status() == ParseResult::INCOMPLETE)
{
- qDebug() << "Parse error: incomplete input:" << ret.message();
+ qDebug() << "Parse error: incomplete input:";
+ sout(pprint(ret, parser));
ReadLine::rl_insert_text("Hello there!");
ReadLine::rl_redisplay();
}
@@ -121,10 +125,9 @@
return false;
}
-ParseResult Repl::tryEvaluate(QString line, QList<AstNode> *expr)
+ParseResult Repl::tryEvaluate(Parser &parser, QList<AstNode> *expr)
{
- Parser parser(line);
- Function func;
+ Function func;
ParseResult ret;
diff --git a/Repl.h b/Repl.h
index 10a922b..3f2bdbe 100644
--- a/Repl.h
+++ b/Repl.h
@@ -19,7 +19,7 @@
QString readLine();
void addHistory(QString line);
ParseResult trySpecialCase(QString line);
- ParseResult tryEvaluate(QString line, QList<AstNode> *expr);
+ ParseResult tryEvaluate(Parser &parser, QList<AstNode> *expr);
Evaluator _eval;
diff --git a/StdLib.cpp b/StdLib.cpp
new file mode 100644
index 0000000..0ad7887
--- /dev/null
+++ b/StdLib.cpp
@@ -0,0 +1,23 @@
+#include "StdLib.h"
+#include "PPrint.h"
+
+StdLib::StdLib()
+{
+ _print.addNativeSentence("e.Expr", [](QList<Token> args)
+ {
+ sout(pprint(args));
+ return args;
+ });
+
+ _prout.addNativeSentence("e.Expr", [](QList<Token> args)
+ {
+ sout(pprint(std::move(args)));
+ return QList<Token>();
+ });
+}
+
+void StdLib::load(Evaluator &eval)
+{
+ eval.addFunction(_print);
+ eval.addFunction(_prout);
+}
diff --git a/StdLib.h b/StdLib.h
new file mode 100644
index 0000000..61fc2a6
--- /dev/null
+++ b/StdLib.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "Function.h"
+#include "Evaluator.h"
+
+class StdLib {
+public:
+ StdLib();
+
+ void load(Evaluator &eval);
+
+private:
+ Function _print{"Print"},
+ _prout{"Prout"};
+};
diff --git a/main.cpp b/main.cpp
index 8e29b11..ce9f435 100644
--- a/main.cpp
+++ b/main.cpp
@@ -45,9 +45,14 @@
g_numFailed++;
qDebug() << "\n\033[31mTEST FAILS:\033[0m";
qDebug() << "Failed to fully parse expression, function or result";
- qDebug() << function << expression << expected;
- qDebug() << funcRet.message() << exprRet.message() << resRet.message();
- qDebug() << funcRet << exprRet << resRet;
+ qDebug() << "Function:";
+ sout(pprint(funcRet, funcParser));
+
+ qDebug() << "Expression:";
+ sout(pprint(exprRet, exprParser));
+
+ qDebug() << "Result:";
+ sout(pprint(resRet, resParser));
goto end;
}
@@ -145,7 +150,7 @@
{
g_numFailed++;
qDebug() << "\n\033[31mTEST FAILS:\033[0m";
- qDebug() << ret.message();
+ sout(pprint(ret, parser));
qDebug() << string;
}
else
@@ -230,6 +235,7 @@
testParseAst("Key = Value");
testParseAst("123", {AstNode("123", 10)});
testParseAst("12 00", {AstNode::fromInteger(12), AstNode::fromInteger(0)});
+ testParseAst("s.A s.B", {AstNode('s', "A"), AstNode('s', "B")});
}
void testAllFunctionDefs()
@@ -255,12 +261,18 @@
cli.addHelpOption();
cli.addVersionOption();
+ cli.addPositionalArgument("script", "REFAL script to run");
+
QCommandLineOption testOption(QStringList{"t", "test"}, "Run test suite.");
cli.addOption(testOption);
cli.process(a);
- if (cli.isSet(testOption))
+ if (cli.positionalArguments().length() > 0)
+ {
+ qDebug() << "Running script" << cli.positionalArguments()[0];
+ }
+ else if (cli.isSet(testOption))
{
testAllMatches();
qDebug() << "";
diff --git a/notes.txt b/notes.txt
index fe6f185..53ef900 100644
--- a/notes.txt
+++ b/notes.txt
@@ -6,7 +6,7 @@
DEPENDENCIES
- Install libedit-dev and libqt5core5a
+ Install libreadline-dev and libqt5core5a
OPTIMIZATION POSSIBILITIES: