initial code upload
parent
a650e3b1c5
commit
8dc1db8fb9
@ -0,0 +1,2 @@
|
||||
*.o
|
||||
main
|
@ -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 $<
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @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 <algorithm>
|
||||
#include <any>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <deque>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <typeinfo>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
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<int>(value))
|
||||
return llvm::ConstantInt::get(state.context, llvm::APInt(sizeof(int) * 8, std::get<int>(value)));
|
||||
else
|
||||
return llvm::ConstantFP::get(state.context, llvm::APFloat(std::get<double>(value)));
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
AST::Value AST::ProcedureCall::codegen(CompilerState& state)
|
||||
{
|
||||
if (auto id = dynamic_cast<AST::Identifier *>(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<llvm::Value *> args;
|
||||
std::vector<llvm::Type *> 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<Node *> operands;
|
||||
return nullptr;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
AST::Value AST::LambdaExpression::codegen(CompilerState& state)
|
||||
{
|
||||
std::vector<llvm::Type *> args;
|
||||
std::vector<std::string> 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;
|
||||
}
|
||||
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @file ast.hpp
|
||||
* @brief Abstract Syntax Tree (AST) implementation.
|
||||
*/
|
||||
|
||||
#ifndef AST_HPP
|
||||
#define AST_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <variant>
|
||||
|
||||
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<int, double> 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<Node *> 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<Identifier *> operands;
|
||||
std::vector<Node *> 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
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @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 <cstdio>
|
||||
#include <string>
|
||||
|
||||
#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<llvm::Reloc::Model>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @file parser.cpp
|
||||
* @brief Source code parser to produce AST.
|
||||
*/
|
||||
|
||||
#include "parser.hpp"
|
||||
|
||||
#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));
|
||||
}
|
||||
|
||||
void Parser::consumeWhitespace()
|
||||
{
|
||||
while (isspace(text.front()))
|
||||
text.pop_front();
|
||||
}
|
||||
|
||||
std::optional<std::string> 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<std::variant<int, double>> 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<AST::Identifier *>(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<AST::Node *> 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<AST::Identifier *> 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<AST::Node *> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @file parser.hpp
|
||||
* @brief Source code parser to produce AST.
|
||||
*/
|
||||
|
||||
#ifndef PARSER_HPP
|
||||
#define PARSER_HPP
|
||||
|
||||
#include "ast.hpp"
|
||||
#include "state.hpp"
|
||||
|
||||
#include <deque>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
class Parser {
|
||||
public:
|
||||
enum Error {
|
||||
None,
|
||||
ExpectedProcedure,
|
||||
ExpectedIdentifier,
|
||||
ExpectedProcedureCallClose,
|
||||
ExpectedArgumentList,
|
||||
UnknownIdentifier,
|
||||
InvalidOperator,
|
||||
InvalidOperand,
|
||||
};
|
||||
|
||||
void addString(const std::string& str);
|
||||
|
||||
void consumeWhitespace();
|
||||
std::optional<std::string> consumeIdentifier();
|
||||
std::optional<std::variant<int, double>> consumeLiteralNumber();
|
||||
|
||||
AST::Node *parse(CompilerState&);
|
||||
|
||||
private:
|
||||
std::deque<char> 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
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @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 <map>
|
||||
#include <string>
|
||||
|
||||
struct CompilerState
|
||||
{
|
||||
llvm::LLVMContext context;
|
||||
llvm::Module module;
|
||||
llvm::IRBuilder<> builder;
|
||||
std::map<std::string, AST::Value> namedValues;
|
||||
int scope;
|
||||
|
||||
CompilerState():
|
||||
context(),
|
||||
module("main", context),
|
||||
builder(context),
|
||||
scope(0) {}
|
||||
};
|
||||
|
||||
#endif // STATE_HPP
|
||||
|
Loading…
Reference in New Issue