From fe904e47f22aed2e79f1a31039739661b65d1750 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sat, 15 Oct 2022 14:18:08 -0400 Subject: [PATCH] Report on caught syntax errors --- .gitignore | 1 + ast.cpp | 5 +- main.cpp | 24 ++++++-- parser.cpp | 170 ++++++++++++++++++++++++++++++++++++++++------------- parser.hpp | 54 +++++++++++++---- 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 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 +#include #include #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 #include #include -#include #include 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 Parser::consumeIdentifier() +std::optional 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> 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 Parser::parse() { - AST::Node *ret = nullptr; + std::deque 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(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 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 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 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 #include @@ -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 consumeIdentifier(); - std::optional> 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 parse(); + + std::string describeErrors() noexcept; - AST::Node *parse(CompilerState&); + bool hasErrors() const noexcept { + return !errors.empty(); + } private: std::deque text; - Error lastError = None; + std::deque 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 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> consumeLiteralNumber(); AST::Node *parseExpression(); AST::Node *parseProcedureCall();