aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClyne Sullivan <clyne@bitgloo.com>2022-10-15 14:18:08 -0400
committerClyne Sullivan <clyne@bitgloo.com>2022-10-15 14:18:08 -0400
commitfe904e47f22aed2e79f1a31039739661b65d1750 (patch)
treeca2da866951efe88b79f2f9711cc93916262a4fd
parent5c14bb190f73c883ce6b1db2b5334886fa304ace (diff)
Report on caught syntax errorsHEADmaster
-rw-r--r--.gitignore1
-rw-r--r--ast.cpp5
-rw-r--r--main.cpp24
-rw-r--r--parser.cpp170
-rw-r--r--parser.hpp54
5 files changed, 196 insertions, 58 deletions
diff --git a/.gitignore b/.gitignore
index f0c9b81..917d2e9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
+.*
*.o
main
diff --git a/ast.cpp b/ast.cpp
index d23ff0d..e3a3e68 100644
--- a/ast.cpp
+++ b/ast.cpp
@@ -80,8 +80,9 @@ AST::Value AST::ProcedureCall::codegen(CompilerState& state)
std::vector<llvm::Type *> argtypes;
for (auto& a : operands) {
- args.push_back(a->codegen(state));
- argtypes.push_back(args.back()->getType());
+ auto gen = a->codegen(state);
+ args.push_back(gen);
+ argtypes.push_back(gen ? gen->getType() : llvm::Type::getVoidTy(state.context));
}
diff --git a/main.cpp b/main.cpp
index c70c976..fa1a242 100644
--- a/main.cpp
+++ b/main.cpp
@@ -59,7 +59,7 @@ TODO:
#include "llvm/Target/TargetMachine.h"
#include "llvm/Target/TargetOptions.h"
-#include <cstdio>
+#include <iostream>
#include <string>
#include "ast.hpp"
@@ -80,14 +80,26 @@ int main(int argc, const char *argv[])
auto block = llvm::BasicBlock::Create(state.context, "entry", func);
state.builder.SetInsertPoint(block);
- parser.parse(state);
+ auto astTree = parser.parse();
+ if (parser.hasErrors()) {
+ auto err = parser.describeErrors();
+ std::cout << "Error: " << std::endl << err << std::endl;
+ } else if (!astTree.empty()) {
+ for (auto& node : astTree) {
+ std::cout << node->desc() << std::endl;
- state.builder.CreateRet(state.builder.getInt32(0));
+ node->codegen(state);
+ }
- puts("");
- state.module.print(llvm::errs(), nullptr); // prints everything
+ state.builder.CreateRet(state.builder.getInt32(0));
- compileToObjectFile(state);
+ std::cout << std::endl;
+ state.module.print(llvm::errs(), nullptr); // prints everything
+
+ compileToObjectFile(state);
+ } else {
+ std::cout << "Nothing to do." << std::endl;
+ }
return 0;
}
diff --git a/parser.cpp b/parser.cpp
index 6d5b992..09f2e6c 100644
--- a/parser.cpp
+++ b/parser.cpp
@@ -24,24 +24,25 @@
#include <algorithm>
#include <cctype>
#include <cstdlib>
-#include <iostream>
#include <vector>
void Parser::addString(const std::string& str)
{
- std::copy(str.cbegin(), str.cend(), std::back_inserter(text));
+ if (!str.empty())
+ std::copy(str.cbegin(), str.cend(), std::back_inserter(text));
}
-void Parser::consumeWhitespace()
+void Parser::consumeWhitespace() noexcept
{
while (isspace(text.front()))
text.pop_front();
}
-std::optional<std::string> Parser::consumeIdentifier()
+std::optional<std::string> Parser::consumeIdentifier() noexcept
{
std::string ret;
+ // TODO Accept all valid identifiers according to R7RS-small.
if (isalpha(text.front())) {
do {
ret += text.front();
@@ -66,32 +67,35 @@ std::optional<std::variant<int, double>> Parser::consumeLiteralNumber()
if (ret.empty())
return {};
- else if (ret.find('.') == std::string::npos)
- return (int)strtol(ret.c_str(), nullptr, 0);
- else
- return strtod(ret.c_str(), nullptr);
+
+ if (ret.find('.') == std::string::npos) {
+ int r = strtol(ret.c_str(), nullptr, 0);
+ // TODO Error check
+ return r;
+ } else {
+ auto r = strtod(ret.c_str(), nullptr);
+ // TODO Error check
+ return r;
+ }
}
-AST::Node *Parser::parse(CompilerState& state)
+std::deque<AST::Node *> Parser::parse()
{
- AST::Node *ret = nullptr;
+ std::deque<AST::Node *> ret;
+ errors.clear();
while (!text.empty()) {
consumeWhitespace();
// At the top-level, there will only be procedure calls.
- ret = parseProcedureCall();
- if (lastError != None)
- return nullptr;
-
-
- std::cout << (ret ? ret->desc() : "nullptr") << std::endl;
-
- if (ret)
- ret->codegen(state);
+ auto node = parseProcedureCall();
+ if (errors.empty() && node) {
+ ret.push_back(node);
+ } else {
+ return {};
+ }
}
- lastError = None;
return ret;
}
@@ -110,6 +114,7 @@ AST::Node *Parser::parseExpression()
return lit;
}
+ errors.push_back(InvalidExpression);
return nullptr;
}
@@ -117,7 +122,7 @@ AST::Node *Parser::parseProcedureCall()
{
// Consume the opening parenthesis.
if (text.front() != '(') {
- lastError = ExpectedProcedure;
+ errors.push_back(ExpectedProcedureCallOpen);
return nullptr;
} else {
text.pop_front();
@@ -125,11 +130,10 @@ AST::Node *Parser::parseProcedureCall()
// Consume the identifier string.
auto ident = parseExpression();
- if (ident == nullptr) {
- lastError = InvalidOperator;
+ if (ident == nullptr)
return nullptr;
- }
+ // Check for special procedure calls.
if (auto id = dynamic_cast<AST::Identifier *>(ident); id) {
if (id->name == "lambda")
return parseLambdaExpression();
@@ -141,13 +145,16 @@ AST::Node *Parser::parseProcedureCall()
return parseAssignment();
}
+ // This is a regular procedure call.
+ // Build the argument list:
+
std::vector<AST::Node *> args;
consumeWhitespace();
while (text.front() != ')') {
auto node = parseExpression();
if (node == nullptr) {
- lastError = InvalidOperand;
+ errors.push_back(InvalidOperand);
return nullptr;
}
@@ -164,7 +171,7 @@ AST::Node *Parser::parseProcedureCall()
return pc;
} else {
- lastError = ExpectedProcedureCallClose;
+ errors.push_back(ExpectedProcedureCallClose);
return nullptr;
}
}
@@ -173,18 +180,24 @@ AST::Node *Parser::parseConditional()
{
consumeWhitespace();
auto cond = parseExpression();
- if (cond == nullptr)
+ if (cond == nullptr) {
+ errors.push_back(InvalidCondition);
return nullptr;
+ }
consumeWhitespace();
auto ift = parseExpression();
- if (ift == nullptr)
+ if (ift == nullptr) {
+ errors.push_back(InvalidThenBranch);
return nullptr;
+ }
consumeWhitespace();
auto iff = parseExpression();
- if (iff == nullptr)
+ if (iff == nullptr) {
+ errors.push_back(InvalidThenBranch);
return nullptr;
+ }
consumeWhitespace();
if (text.front() == ')') {
@@ -195,9 +208,10 @@ AST::Node *Parser::parseConditional()
node->iftrue = ift;
node->iffalse = iff;
return node;
- }
-
- return nullptr;
+ } else {
+ errors.push_back(ExpectedProcedureCallClose);
+ return nullptr;
+ }
}
AST::Node *Parser::parseDefinition()
@@ -221,8 +235,14 @@ AST::Node *Parser::parseDefinition()
def->ident = id;
def->value = val;
return def;
+ } else {
+ errors.push_back(ExpectedProcedureCallClose);
}
+ } else {
+ errors.push_back(InvalidInitializer);
}
+ } else {
+ errors.push_back(ExpectedIdentifier);
}
return nullptr;
@@ -249,8 +269,14 @@ AST::Node *Parser::parseAssignment()
def->ident = id;
def->value = val;
return def;
+ } else {
+ errors.push_back(ExpectedProcedureCallClose);
}
+ } else {
+ errors.push_back(InvalidAssignValue);
}
+ } else {
+ errors.push_back(ExpectedIdentifier);
}
return nullptr;
@@ -258,15 +284,17 @@ AST::Node *Parser::parseAssignment()
AST::Node *Parser::parseLambdaExpression()
{
- // First get argument list
+ // Consume beginning of argument list.
consumeWhitespace();
if (text.front() != '(') {
- lastError = ExpectedArgumentList;
+ errors.push_back(ExpectedArgumentList);
return nullptr;
} else {
text.pop_front();
}
+ // Consume argument list:
+
std::vector<AST::Identifier *> args;
while (text.front() != ')') {
@@ -276,22 +304,28 @@ AST::Node *Parser::parseLambdaExpression()
ident->name = *arg;
args.push_back(ident);
} else {
- lastError = InvalidOperand;
+ errors.push_back(InvalidArgumentName);
return nullptr;
}
consumeWhitespace();
}
- text.pop_front(); // consume arg list ')'
+
+ // Consume arg list closing ')' that must be there.
+ text.pop_front();
+
+ // Consume lambda body:
std::vector<AST::Node *> body;
- // Next, consume function body.
consumeWhitespace();
while (text.front() != ')') {
auto exp = parseExpression();
- //if (exp == nullptr) // TODO commands/definitions are okay
- // return nullptr;
+
+ if (!errors.empty()) {
+ errors.push_back(InvalidLambdaBody);
+ return nullptr;
+ }
body.push_back(exp);
consumeWhitespace();
@@ -306,8 +340,64 @@ AST::Node *Parser::parseLambdaExpression()
return le;
} else {
- lastError = ExpectedProcedureCallClose;
+ errors.push_back(ExpectedProcedureCallClose);
return nullptr;
}
}
+std::string Parser::describeErrors() noexcept
+{
+ std::string ret;
+
+ for (auto& err : errors) {
+ switch (err) {
+ case ExpectedProcedureCallOpen:
+ ret += "Expected opening \'(\' for procedure call.\n";
+ break;
+ case ExpectedIdentifier:
+ ret += "Expected a valid identifier.\n";
+ break;
+ case ExpectedProcedureCallClose:
+ ret += "Expected closing \')\' for procedure call.\n";
+ break;
+ case ExpectedArgumentList:
+ ret += "Expected beginning of argument list.\n";
+ break;
+ case UnknownIdentifier:
+ ret += "Given identifier is not valid.\n";
+ break;
+ case InvalidExpression:
+ ret += "Expected a valid expression.\n";
+ break;
+ case InvalidOperand:
+ ret += "Given invalid argument or operand.\n";
+ break;
+ case InvalidCondition:
+ ret += "Given invalid condition for conditional statement.\n";
+ break;
+ case InvalidThenBranch:
+ ret += "Given invalid \"then\" branch for conditional statement.\n";
+ break;
+ case InvalidElseBranch:
+ ret += "Given invalid \"else\" branch for conditional statement.\n";
+ break;
+ case InvalidInitializer:
+ ret += "Given invalid initializer for a declaration.\n";
+ break;
+ case InvalidAssignValue:
+ ret += "Given invalid value for an assignment.\n";
+ break;
+ case InvalidArgumentName:
+ ret += "Given invalid name for an argument.\n";
+ break;
+ case InvalidLambdaBody:
+ ret += "Lambda body is invalid.\n";
+ break;
+ default:
+ break;
+ }
+ }
+
+ return ret;
+}
+
diff --git a/parser.hpp b/parser.hpp
index 65f2686..2ecf6f8 100644
--- a/parser.hpp
+++ b/parser.hpp
@@ -23,7 +23,6 @@
#define PARSER_HPP
#include "ast.hpp"
-#include "state.hpp"
#include <deque>
#include <optional>
@@ -33,27 +32,62 @@
class Parser {
public:
enum Error {
- None,
- ExpectedProcedure,
+ ExpectedProcedureCallOpen,
ExpectedIdentifier,
ExpectedProcedureCallClose,
ExpectedArgumentList,
UnknownIdentifier,
- InvalidOperator,
+ InvalidExpression,
InvalidOperand,
+ InvalidCondition,
+ InvalidThenBranch,
+ InvalidElseBranch,
+ InvalidInitializer,
+ InvalidAssignValue,
+ InvalidArgumentName,
+ InvalidLambdaBody
};
- void addString(const std::string& str);
+ /**
+ * Appends the given string to the end of the text queue.
+ * Call parse() after adding text to produce the AST.
+ */
+ void addString(const std::string&);
- void consumeWhitespace();
- std::optional<std::string> consumeIdentifier();
- std::optional<std::variant<int, double>> consumeLiteralNumber();
+ /**
+ * Attempts to parse all text in the text queue, stopping if there is an
+ * error.
+ * @return An iterable collection of produced AST nodes.
+ */
+ std::deque<AST::Node *> parse();
+
+ std::string describeErrors() noexcept;
- AST::Node *parse(CompilerState&);
+ bool hasErrors() const noexcept {
+ return !errors.empty();
+ }
private:
std::deque<char> text;
- Error lastError = None;
+ std::deque<Error> errors;
+
+ /**
+ * Advances through the text queue until a non-whitespace character is
+ * found.
+ */
+ void consumeWhitespace() noexcept;
+ /**
+ * Attempts to consume an identifier from the text queue.
+ * @return A string containing the identifier if one is found.
+ */
+ std::optional<std::string> consumeIdentifier() noexcept;
+ /**
+ * Attempts to consume a literal number from the text queue.
+ * @return The number if one is found: an integer if there is no decimal
+ * point; otherwise, a double.
+ * TODO Ensure and add noexcept.
+ */
+ std::optional<std::variant<int, double>> consumeLiteralNumber();
AST::Node *parseExpression();
AST::Node *parseProcedureCall();