]> code.bitgloo.com Git - clyne/lisp-compiler.git/commitdiff
Report on caught syntax errors master
authorClyne Sullivan <clyne@bitgloo.com>
Sat, 15 Oct 2022 18:18:08 +0000 (14:18 -0400)
committerClyne Sullivan <clyne@bitgloo.com>
Sat, 15 Oct 2022 18:18:08 +0000 (14:18 -0400)
.gitignore
ast.cpp
main.cpp
parser.cpp
parser.hpp

index f0c9b8122456fb3f2152d72e36d0628bba71fa97..917d2e9af5ab7ec71c75bcab501b6e19bdc38a6a 100644 (file)
@@ -1,2 +1,3 @@
+.*
 *.o
 main
diff --git a/ast.cpp b/ast.cpp
index d23ff0dbfc4f94e9c20d62247604db4507524d89..e3a3e68f040eba9c249d4209fb418ad7adab59bc 100644 (file)
--- 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));
             }
 
 
index c70c976f5092512f425102f00b38f36aa0fe5981..fa1a242cca584395d2d5cd434ca0fdac11dd2671 100644 (file)
--- 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;
 }
index 6d5b992020054b3d6286bb1111c999c36106d0be..09f2e6cf3364efdc7ad34a58cbdc60b6ecbcf2f4 100644 (file)
 #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;
+}
+
index 65f2686b0577e10ca33803fe359f1430d3aa1cad..2ecf6f876122454842c99b4699c28f440902e521 100644 (file)
@@ -23,7 +23,6 @@
 #define PARSER_HPP
 
 #include "ast.hpp"
-#include "state.hpp"
 
 #include <deque>
 #include <optional>
 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();