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: