From 8dc1db8fb95b082c3911f9ae5bdf4f9815ddec03 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sat, 15 Oct 2022 11:33:08 -0400 Subject: [PATCH] initial code upload --- .gitignore | 2 + LICENSE | 30 ----- Makefile | 24 ++++ ast.cpp | 228 ++++++++++++++++++++++++++++++++++++++ ast.hpp | 183 +++++++++++++++++++++++++++++++ main.cpp | 128 ++++++++++++++++++++++ parser.cpp | 313 +++++++++++++++++++++++++++++++++++++++++++++++++++++ parser.hpp | 67 ++++++++++++ state.hpp | 50 +++++++++ 9 files changed, 995 insertions(+), 30 deletions(-) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 ast.cpp create mode 100644 ast.hpp create mode 100644 main.cpp create mode 100644 parser.cpp create mode 100644 parser.hpp create mode 100644 state.hpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f0c9b81 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +main diff --git a/LICENSE b/LICENSE index d41c0bd..847984e 100644 --- a/LICENSE +++ b/LICENSE @@ -200,33 +200,3 @@ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY C 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. -END OF TERMS AND CONDITIONS - -How to Apply These Terms to Your New Programs - -If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. - -To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - -If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”. - -You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . - -The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fb6c8a9 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +CXX := g++ +CXXFLAGS := -std=c++20 -O0 -ggdb -g3 +LIBS := -lLLVM-14 + +SRC := ast.cpp \ + parser.cpp \ + main.cpp + +OBJ := $(subst .cpp,.o,$(SRC)) +BIN := main + +all: $(BIN) + +clean: + @echo " CLEAN" + @rm -f $(BIN) $(OBJ) + +$(BIN): $(OBJ) + @echo " LD " $@ + @$(CXX) $(CXXFLAGS) -o $@ $^ $(LIBS) + +.cpp.o: + @echo " CXX " $< + @$(CXX) $(CXXFLAGS) -c $< diff --git a/ast.cpp b/ast.cpp new file mode 100644 index 0000000..d23ff0d --- /dev/null +++ b/ast.cpp @@ -0,0 +1,228 @@ +/** + * lisp-compiler: Compiles LISP using LLVM. + * Copyright (C) 2022 Clyne Sullivan + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * @file ast.cpp + * @brief Abstract Syntax Tree (AST) implementation. + */ + +#include "ast.hpp" +#include "state.hpp" + +#include "llvm/ADT/APFloat.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/Type.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +AST::Value AST::Identifier::codegen(CompilerState& state) +{ + if (state.namedValues.contains(name)) + return state.namedValues[name]; + else + return nullptr; +} + +AST::Value AST::Literal::codegen(CompilerState& state) +{ + if (type == AST::Literal::Type::Number) { + if (std::holds_alternative(value)) + return llvm::ConstantInt::get(state.context, llvm::APInt(sizeof(int) * 8, std::get(value))); + else + return llvm::ConstantFP::get(state.context, llvm::APFloat(std::get(value))); + } else { + return nullptr; + } +} + +AST::Value AST::ProcedureCall::codegen(CompilerState& state) +{ + if (auto id = dynamic_cast(callee); id) { + if (state.namedValues.contains(id->name)) { + auto ptrToFuncPtr = state.namedValues[id->name]; + auto funcPtr = state.builder.CreateLoad( + ptrToFuncPtr->getType()->getContainedType(0), + ptrToFuncPtr); + auto funcType = (llvm::FunctionType *)funcPtr->getType()->getContainedType(0); + return state.builder.CreateCall(funcType, funcPtr, llvm::None, "calltmp"); + } else { + std::vector args; + std::vector argtypes; + + for (auto& a : operands) { + args.push_back(a->codegen(state)); + argtypes.push_back(args.back()->getType()); + } + + + auto func = state.module.getOrInsertFunction(id->name, + llvm::FunctionType::get( + llvm::Type::getVoidTy(state.context), + argtypes, + false)); + return state.builder.CreateCall(func, args); + } + + // work off of id's name + // builtin? + // named value? (i.e. is defined) + return nullptr; + } else if (auto v = callee->codegen(state); v) { + // v needs to be a callable procedure + //std::vector operands; + return nullptr; + } else { + return nullptr; + } +} + +AST::Value AST::LambdaExpression::codegen(CompilerState& state) +{ + std::vector args; + std::vector argnames; + + for (auto& op : operands) { + args.push_back(llvm::Type::getDoubleTy(state.context)); + argnames.push_back(op->name); + } + + auto ftype = llvm::FunctionType::get( + llvm::Type::getDoubleTy(state.context), args, false); + auto func = llvm::Function::Create( + ftype, llvm::Function::ExternalLinkage, "lambda", &state.module); + + auto n = argnames.cbegin(); + for (auto& a : func->args()) { + a.setName(*n); + state.namedValues[*n] = &a; + ++n; + } + + auto block = llvm::BasicBlock::Create(state.context, "entry", func); + + auto ip = state.builder.saveIP(); + state.builder.SetInsertPoint(block); + ++state.scope; + + llvm::Value *ret; + for (auto& b : body) + ret = b->codegen(state); + + if (ret) + state.builder.CreateRet(ret); + else + state.builder.CreateRetVoid(); + + --state.scope; + state.builder.restoreIP(ip); + + for (auto& a : argnames) + state.namedValues.erase(a); + + return func; +} + +AST::Value AST::Conditional::codegen(CompilerState& state) +{ + auto cval = state.builder.CreateFCmpONE( + cond->codegen(state), + llvm::ConstantFP::get(state.context, llvm::APFloat(0.0)), + "ifcond"); + + auto func = state.builder.GetInsertBlock()->getParent(); + + auto bthen = llvm::BasicBlock::Create(state.context, "then", func); + auto belse = llvm::BasicBlock::Create(state.context, "else", func); + auto bcont = llvm::BasicBlock::Create(state.context, "cont", func); + state.builder.CreateCondBr(cval, bthen, belse); + + state.builder.SetInsertPoint(bthen); + auto vthen = iftrue->codegen(state); + if (!vthen || vthen->getType() != llvm::Type::getDoubleTy(state.context)) + vthen = llvm::ConstantFP::get(state.context, llvm::APFloat(0.0)); + state.builder.CreateBr(bcont); + + state.builder.SetInsertPoint(belse); + auto velse = iffalse->codegen(state); + if (!velse || velse->getType() != llvm::Type::getDoubleTy(state.context)) + velse = llvm::ConstantFP::get(state.context, llvm::APFloat(0.0)); + state.builder.CreateBr(bcont); + + state.builder.SetInsertPoint(bcont); + auto PN = state.builder.CreatePHI( + llvm::Type::getDoubleTy(state.context), 2, "iftmp"); + + PN->addIncoming(vthen, bthen); + PN->addIncoming(velse, belse); + return PN; +} + +AST::Value AST::Definition::codegen(CompilerState& state) +{ + if (!state.namedValues.contains(ident->name)) { + if (state.scope == 0) { + auto val = (llvm::Constant *)value->codegen(state); + + state.module.getOrInsertGlobal(ident->name, val->getType()); + + auto var = state.module.getNamedGlobal(ident->name); + var->setLinkage(llvm::Function::ExternalLinkage); + var->setInitializer(val); + state.namedValues[ident->name] = var; + return var; + } else { + auto alloc = state.builder.CreateAlloca( + llvm::Type::getDoubleTy(state.context), + nullptr, + ident->name); + state.builder.CreateStore(value->codegen(state), alloc); + state.namedValues[ident->name] = alloc; + return alloc; + } + } else { + return nullptr; + } +} + +AST::Value AST::Assignment::codegen(CompilerState& state) +{ + if (state.scope > 0) { + if (state.namedValues.contains(ident->name)) { + return state.builder.CreateStore(value->codegen(state), state.namedValues[ident->name]); + } + } + + return nullptr; +} + + diff --git a/ast.hpp b/ast.hpp new file mode 100644 index 0000000..0310f3f --- /dev/null +++ b/ast.hpp @@ -0,0 +1,183 @@ +/** + * lisp-compiler: Compiles LISP using LLVM. + * Copyright (C) 2022 Clyne Sullivan + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * @file ast.hpp + * @brief Abstract Syntax Tree (AST) implementation. + */ + +#ifndef AST_HPP +#define AST_HPP + +#include +#include +#include + +struct CompilerState; + +namespace llvm { + struct Value; +} + +namespace AST { + +using Value = llvm::Value *; + +struct Node +{ + virtual ~Node() {}; + + virtual Value codegen(CompilerState&) = 0; + virtual std::string desc() { + return "Node"; + } +}; + +struct Identifier : public Node +{ + std::string name; + + virtual Value codegen(CompilerState&) final; + + virtual std::string desc() final { + return std::string("Identifier: ") + name; + } +}; + +struct Literal : public Node +{ + enum Type { + Unknown, + Number, + } type = Unknown; + std::variant value; + + virtual Value codegen(CompilerState&) final; + + virtual std::string desc() final { + std::string str ("Literal: "); + + switch (type) { + default: + case Unknown: + str += "unknown"; + break; + case Number: + str += std::visit([](auto&& v) { return std::to_string(v); }, value); + break; + } + + return str; + } +}; + +struct ProcedureCall : public Node +{ + Node *callee; + std::vector operands; + + virtual Value codegen(CompilerState&) final; + + virtual std::string desc() final { + std::string str ("ProcedureCall: "); + + str = str + "<" + (callee ? callee->desc() : "unknown") + ">"; + str += '('; + if (!operands.empty()) { + for (const auto& op : operands) { + str = str + "<" + (op ? op->desc() : "unknown") + ">"; + str += ", "; + } + + str.pop_back(); + str.pop_back(); + } + str += ')'; + + return str; + } +}; + +struct LambdaExpression : public Node +{ + std::vector operands; + std::vector body; + + virtual Value codegen(CompilerState&) final; + + virtual std::string desc() final { + return "LambdaExpression"; + } +}; + +struct Conditional : public Node +{ + Node *cond; + Node *iftrue; + Node *iffalse; + + virtual Value codegen(CompilerState&) final; + + virtual std::string desc() final { + return "Conditional"; + } +}; + +struct Definition : public Node +{ + Identifier *ident; + Node *value; + + virtual Value codegen(CompilerState&) final; + + virtual std::string desc() final { + return "Definition"; + } +}; + +struct Assignment : public Node +{ + Identifier *ident; + Node *value; + + virtual Value codegen(CompilerState&) final; + + virtual std::string desc() final { + return "Assignment"; + } +}; + +struct DerivedExpression : public Node +{ + std::string name; + + virtual Value codegen(CompilerState&) final { + return nullptr; + } + + virtual std::string desc() final { + return "DerivedExpression"; + } +}; + +// MacroUse +// MacroBlock +// Includer + +} + +#endif // AST_HPP + diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..c70c976 --- /dev/null +++ b/main.cpp @@ -0,0 +1,128 @@ +/** + * lisp-compiler: Compiles LISP using LLVM. + * Copyright (C) 2022 Clyne Sullivan + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * @file main.cpp + * @brief Program entry point. + */ + +/* + +Current support: + - Literals: + - Integer + - Double (when decimal point is found) + - (define name value) + - (set! defined-name new-value) + - (lambda (...) ...) + - (if cond then else) + - Calling procedures: + - Defined lambdas + - Built-in things + - Undefined procedures are prototyped + +TODO: + - Error reporting! + - Documentation! + - Arithmetic + - String literals? + - Typed definitions... + - See R7RS-small for more... + +*/ + +#include "llvm/ADT/STLExtras.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/LegacyPassManager.h" +#include "llvm/IR/Type.h" +#include "llvm/IR/Verifier.h" +#include "llvm/MC/TargetRegistry.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Host.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Target/TargetMachine.h" +#include "llvm/Target/TargetOptions.h" + +#include +#include + +#include "ast.hpp" +#include "parser.hpp" +#include "state.hpp" + +void compileToObjectFile(CompilerState&); +int main(int argc, const char *argv[]) +{ + CompilerState state; + Parser parser; + + for (int i = 1; i < argc; ++i) + parser.addString(argv[i]); + + auto functype = llvm::FunctionType::get(llvm::Type::getInt32Ty(state.context), {}, false); + auto func = llvm::Function::Create(functype, llvm::Function::ExternalLinkage, "main", &state.module); + auto block = llvm::BasicBlock::Create(state.context, "entry", func); + state.builder.SetInsertPoint(block); + + parser.parse(state); + + state.builder.CreateRet(state.builder.getInt32(0)); + + puts(""); + state.module.print(llvm::errs(), nullptr); // prints everything + + compileToObjectFile(state); + + return 0; +} + +void compileToObjectFile(CompilerState& state) +{ + llvm::InitializeAllTargetInfos(); + llvm::InitializeAllTargets(); + llvm::InitializeAllTargetMCs(); + llvm::InitializeAllAsmParsers(); + llvm::InitializeAllAsmPrinters(); + + auto TargetTriple = "x86_64-pc-linux-gnu"; + + std::string error; + auto Target = llvm::TargetRegistry::lookupTarget(TargetTriple, error); + if (!error.empty()) + puts(error.c_str()); + + auto CPU = "generic"; + auto Features = ""; + llvm::TargetOptions opt; + auto RM = llvm::Optional(); + auto TargetMachine = Target->createTargetMachine(TargetTriple, CPU, Features, opt, RM); + + std::error_code EC; + llvm::raw_fd_ostream dest ("out.o", EC, llvm::sys::fs::OF_None); + + llvm::legacy::PassManager pass; + + if (TargetMachine->addPassesToEmitFile(pass, dest, nullptr, llvm::CGFT_ObjectFile)) { + llvm::errs() << "TargetMachine can't emit a file of this type"; + } else { + pass.run(state.module); + dest.flush(); + } +} + diff --git a/parser.cpp b/parser.cpp new file mode 100644 index 0000000..6d5b992 --- /dev/null +++ b/parser.cpp @@ -0,0 +1,313 @@ +/** + * lisp-compiler: Compiles LISP using LLVM. + * Copyright (C) 2022 Clyne Sullivan + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * @file parser.cpp + * @brief Source code parser to produce AST. + */ + +#include "parser.hpp" + +#include +#include +#include +#include +#include + +void Parser::addString(const std::string& str) +{ + std::copy(str.cbegin(), str.cend(), std::back_inserter(text)); +} + +void Parser::consumeWhitespace() +{ + while (isspace(text.front())) + text.pop_front(); +} + +std::optional Parser::consumeIdentifier() +{ + std::string ret; + + if (isalpha(text.front())) { + do { + ret += text.front(); + text.pop_front(); + } while (isalnum(text.front()) || text.front() == '!'); + } + + if (ret.empty()) + return {}; + else + return ret; +} + +std::optional> Parser::consumeLiteralNumber() +{ + std::string ret; + + while (isdigit(text.front()) || text.front() == '.') { + ret += text.front(); + text.pop_front(); + } + + 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); +} + +AST::Node *Parser::parse(CompilerState& state) +{ + AST::Node *ret = nullptr; + + 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); + } + + lastError = None; + return ret; +} + +AST::Node *Parser::parseExpression() +{ + if (text.front() == '(') { + return parseProcedureCall(); + } else if (auto id = consumeIdentifier(); id) { + auto ident = new AST::Identifier; + ident->name = *id; + return ident; + } else if (auto d = consumeLiteralNumber(); d) { + auto lit = new AST::Literal; + lit->type = AST::Literal::Number; + lit->value = *d; + return lit; + } + + return nullptr; +} + +AST::Node *Parser::parseProcedureCall() +{ + // Consume the opening parenthesis. + if (text.front() != '(') { + lastError = ExpectedProcedure; + return nullptr; + } else { + text.pop_front(); + } + + // Consume the identifier string. + auto ident = parseExpression(); + if (ident == nullptr) { + lastError = InvalidOperator; + return nullptr; + } + + if (auto id = dynamic_cast(ident); id) { + if (id->name == "lambda") + return parseLambdaExpression(); + else if (id->name == "define") + return parseDefinition(); + else if (id->name == "if") + return parseConditional(); + else if (id->name == "set!") + return parseAssignment(); + } + + std::vector args; + + consumeWhitespace(); + while (text.front() != ')') { + auto node = parseExpression(); + if (node == nullptr) { + lastError = InvalidOperand; + return nullptr; + } + + args.push_back(node); + consumeWhitespace(); + } + + if (text.front() == ')') { + text.pop_front(); + + auto pc = new AST::ProcedureCall; + pc->callee = ident; + pc->operands = args; + + return pc; + } else { + lastError = ExpectedProcedureCallClose; + return nullptr; + } +} + +AST::Node *Parser::parseConditional() +{ + consumeWhitespace(); + auto cond = parseExpression(); + if (cond == nullptr) + return nullptr; + + consumeWhitespace(); + auto ift = parseExpression(); + if (ift == nullptr) + return nullptr; + + consumeWhitespace(); + auto iff = parseExpression(); + if (iff == nullptr) + return nullptr; + + consumeWhitespace(); + if (text.front() == ')') { + text.pop_front(); + + auto node = new AST::Conditional; + node->cond = cond; + node->iftrue = ift; + node->iffalse = iff; + return node; + } + + return nullptr; +} + +AST::Node *Parser::parseDefinition() +{ + consumeWhitespace(); + auto ident = consumeIdentifier(); + + if (ident) { + consumeWhitespace(); + + auto val = parseExpression(); + if (val) { + consumeWhitespace(); + if (text.front() == ')') { + text.pop_front(); + + auto id = new AST::Identifier; + id->name = *ident; + + auto def = new AST::Definition; + def->ident = id; + def->value = val; + return def; + } + } + } + + return nullptr; +} + +AST::Node *Parser::parseAssignment() +{ + consumeWhitespace(); + auto ident = consumeIdentifier(); + + if (ident) { + consumeWhitespace(); + + auto val = parseExpression(); + if (val) { + consumeWhitespace(); + if (text.front() == ')') { + text.pop_front(); + + auto id = new AST::Identifier; + id->name = *ident; + + auto def = new AST::Assignment; + def->ident = id; + def->value = val; + return def; + } + } + } + + return nullptr; +} + +AST::Node *Parser::parseLambdaExpression() +{ + // First get argument list + consumeWhitespace(); + if (text.front() != '(') { + lastError = ExpectedArgumentList; + return nullptr; + } else { + text.pop_front(); + } + + std::vector args; + + while (text.front() != ')') { + auto arg = consumeIdentifier(); + if (arg) { + auto ident = new AST::Identifier; + ident->name = *arg; + args.push_back(ident); + } else { + lastError = InvalidOperand; + return nullptr; + } + + consumeWhitespace(); + } + text.pop_front(); // consume arg list ')' + + std::vector body; + + // Next, consume function body. + consumeWhitespace(); + while (text.front() != ')') { + auto exp = parseExpression(); + //if (exp == nullptr) // TODO commands/definitions are okay + // return nullptr; + + body.push_back(exp); + consumeWhitespace(); + } + + if (text.front() == ')') { + text.pop_front(); + + auto le = new AST::LambdaExpression; + le->operands = args; + le->body = body; + + return le; + } else { + lastError = ExpectedProcedureCallClose; + return nullptr; + } +} + diff --git a/parser.hpp b/parser.hpp new file mode 100644 index 0000000..65f2686 --- /dev/null +++ b/parser.hpp @@ -0,0 +1,67 @@ +/** + * lisp-compiler: Compiles LISP using LLVM. + * Copyright (C) 2022 Clyne Sullivan + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * @file parser.hpp + * @brief Source code parser to produce AST. + */ + +#ifndef PARSER_HPP +#define PARSER_HPP + +#include "ast.hpp" +#include "state.hpp" + +#include +#include +#include +#include + +class Parser { +public: + enum Error { + None, + ExpectedProcedure, + ExpectedIdentifier, + ExpectedProcedureCallClose, + ExpectedArgumentList, + UnknownIdentifier, + InvalidOperator, + InvalidOperand, + }; + + void addString(const std::string& str); + + void consumeWhitespace(); + std::optional consumeIdentifier(); + std::optional> consumeLiteralNumber(); + + AST::Node *parse(CompilerState&); + +private: + std::deque text; + Error lastError = None; + + AST::Node *parseExpression(); + AST::Node *parseProcedureCall(); + AST::Node *parseConditional(); + AST::Node *parseDefinition(); + AST::Node *parseAssignment(); + AST::Node *parseLambdaExpression(); +}; + +#endif // PARSER_HPP + diff --git a/state.hpp b/state.hpp new file mode 100644 index 0000000..086854e --- /dev/null +++ b/state.hpp @@ -0,0 +1,50 @@ +/** + * lisp-compiler: Compiles LISP using LLVM. + * Copyright (C) 2022 Clyne Sullivan + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * @file state.hpp + * @brief Object to maintain compiler state during code compilation. + */ + +#ifndef STATE_HPP +#define STATE_HPP + +#include "ast.hpp" + +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Module.h" + +#include +#include + +struct CompilerState +{ + llvm::LLVMContext context; + llvm::Module module; + llvm::IRBuilder<> builder; + std::map namedValues; + int scope; + + CompilerState(): + context(), + module("main", context), + builder(context), + scope(0) {} +}; + +#endif // STATE_HPP +