]> git.leonardobizzoni.com Git - LBPL/commitdiff
Finished interpreting class related statements
authorLeonardoBizzoni <leo2002714@gmail.com>
Sun, 3 Sep 2023 10:40:51 +0000 (12:40 +0200)
committerLeonardoBizzoni <leo2002714@gmail.com>
Sun, 3 Sep 2023 10:40:51 +0000 (12:40 +0200)
37 files changed:
.gitignore [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0755]
README.org [new file with mode: 0755]
lib/LBPLCallable.h [new file with mode: 0644]
lib/LBPLClass.h [new file with mode: 0644]
lib/LBPLFunction.h [new file with mode: 0644]
lib/LBPLInstance.h [new file with mode: 0644]
lib/LBPLTypes.h [new file with mode: 0644]
lib/ast_printer.h [new file with mode: 0755]
lib/builtin_methods.h [new file with mode: 0644]
lib/common.h [new file with mode: 0755]
lib/environment.h [new file with mode: 0755]
lib/expressions.h [new file with mode: 0755]
lib/interpreter.h [new file with mode: 0644]
lib/lexer.h [new file with mode: 0755]
lib/main.h [new file with mode: 0755]
lib/parser.h [new file with mode: 0755]
lib/resolver.h [new file with mode: 0644]
lib/runtime_error.h [new file with mode: 0644]
lib/statements.h [new file with mode: 0755]
lib/syntax_error.h [new file with mode: 0755]
lib/token.h [new file with mode: 0755]
lib/token_type.h [new file with mode: 0755]
lib/tree_nodes.h [new file with mode: 0644]
lib/visitor.h [new file with mode: 0755]
src/LBPLClass.cpp [new file with mode: 0644]
src/LBPLFunction.cpp [new file with mode: 0644]
src/LBPLInstance.cpp [new file with mode: 0644]
src/ast_printer.cpp [new file with mode: 0755]
src/environment.cpp [new file with mode: 0755]
src/interpreter.cpp [new file with mode: 0644]
src/lexer.cpp [new file with mode: 0755]
src/main.cpp [new file with mode: 0755]
src/parser.cpp [new file with mode: 0755]
src/resolver.cpp [new file with mode: 0644]
src/runtime_error.cpp [new file with mode: 0644]
src/syntax_error.cpp [new file with mode: 0755]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..5b79a54
--- /dev/null
@@ -0,0 +1,2 @@
+build/
+.cache/
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100755 (executable)
index 0000000..b93afa3
--- /dev/null
@@ -0,0 +1,14 @@
+cmake_minimum_required (VERSION 3.26.4)
+
+add_compile_options(-O2)
+
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS OFF)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+project (lbpl VERSION 0.1.0)
+
+include_directories(lib)
+
+file(GLOB SRCFILES src/*.cpp)
+add_executable(${PROJECT_NAME} ${SRCFILES})
diff --git a/README.org b/README.org
new file mode 100755 (executable)
index 0000000..e8d4a28
--- /dev/null
@@ -0,0 +1,37 @@
+* Running it
+#+begin_src 
+git clone https://github.com/LeonardoBizzoni/LBPL.git
+cd LBPL
+mkdir build
+cd build
+cmake ..
+make
+#+end_src
+
+* Example script
+#+begin_src 
+fn fib(n) {
+    if (n < 2) {
+        return n;
+    }
+    return fib(n-1)+fib(n-2);
+}
+
+class Prova {
+    init(n) {
+        let start = clock();
+        fib(n);
+        let end = clock();
+
+        println("fib(26) runtime: " + (end-start));
+    }
+      
+    prova() { println("prova"); }
+}
+
+let a = Prova(25);
+a.var1 = 10;
+println(a.var1);
+a.var1 = 210;
+println(a.var1);
+#+end_src
diff --git a/lib/LBPLCallable.h b/lib/LBPLCallable.h
new file mode 100644 (file)
index 0000000..b858e69
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef LBPL_CALLABLE_H
+#define LBPL_CALLABLE_H
+
+#include "LBPLTypes.h"
+#include <vector>
+
+class Interpreter;
+
+class LBPLCallable {
+public:
+  virtual int arity() = 0;
+  virtual LBPLType call(Interpreter*, std::vector<LBPLType>&) = 0;
+};
+
+#endif
diff --git a/lib/LBPLClass.h b/lib/LBPLClass.h
new file mode 100644 (file)
index 0000000..58ed0ac
--- /dev/null
@@ -0,0 +1,29 @@
+#ifndef LBPL_CLASS_H
+#define LBPL_CLASS_H
+
+#include "LBPLCallable.h"
+#include "LBPLFunction.h"
+
+#include <map>
+#include <memory>
+
+class LBPLClass : public LBPLCallable {
+public:
+  std::string name;
+  std::shared_ptr<LBPLClass> superclass;
+  std::map<std::string, LBPLFunc *> methods;
+
+public:
+  LBPLClass(const std::string &name, std::shared_ptr<LBPLClass> &superclass,
+            std::map<std::string, LBPLFunc *> &methods)
+      : name(name), superclass(superclass), methods(methods) {}
+  LBPLClass(const std::string &name, std::map<std::string, LBPLFunc *> &methods)
+      : name(name), superclass(nullptr), methods(methods) {}
+
+  LBPLFunc *findMethod(const std::string &);
+
+  int arity() override;
+  LBPLType call(Interpreter *, std::vector<LBPLType> &) override;
+};
+
+#endif
diff --git a/lib/LBPLFunction.h b/lib/LBPLFunction.h
new file mode 100644 (file)
index 0000000..de661e1
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef LBPL_FUNCTION_H
+#define LBPL_FUNCTION_H
+
+#include "LBPLCallable.h"
+#include "environment.h"
+#include "statements.h"
+#include <memory>
+
+class LBPLFunc : public LBPLCallable {
+private:
+  FnStmt *stmt;
+  std::shared_ptr<Environment> closureEnv;
+  bool isInitializer;
+
+public:
+  LBPLFunc(FnStmt *stmt, std::shared_ptr<Environment> &closureEnv,
+           bool isInitializer)
+      : stmt(stmt), closureEnv(closureEnv), isInitializer(isInitializer) {}
+  LBPLFunc(FnStmt *stmt, std::shared_ptr<Environment> &&closureEnv,
+           bool isInitializer)
+      : stmt(stmt), closureEnv(closureEnv), isInitializer(isInitializer) {}
+
+  void bind(std::shared_ptr<LBPLInstance> &&instance);
+  void bind(std::shared_ptr<LBPLInstance> &instance);
+
+  int arity() override;
+  LBPLType call(Interpreter *, std::vector<LBPLType> &) override;
+};
+
+#endif
diff --git a/lib/LBPLInstance.h b/lib/LBPLInstance.h
new file mode 100644 (file)
index 0000000..a463142
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef LBPL_INSTANCE_H
+#define LBPL_INSTANCE_H
+
+#include "LBPLClass.h"
+#include "runtime_error.h"
+
+#include <map>
+#include <memory>
+
+class LBPLInstance {
+private:
+  LBPLClass *lbplClass;
+  std::map<std::string, LBPLType> fields;
+
+public:
+  LBPLInstance(LBPLClass *lbplClass) : lbplClass(lbplClass), fields() {}
+  LBPLInstance(LBPLInstance *other)
+      : lbplClass(other->lbplClass), fields(other->fields) {}
+
+  LBPLType get(const Token *name);
+  void set(const Token *name, LBPLType &value);
+};
+
+#endif
diff --git a/lib/LBPLTypes.h b/lib/LBPLTypes.h
new file mode 100644 (file)
index 0000000..d37fa91
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef LBPL_TYPES_H
+#define LBPL_TYPES_H
+
+#include <memory>
+#include <string>
+#include <variant>
+
+class LBPLInstance;
+class LBPLCallable;
+class LBPLClass;
+
+using LBPLType =
+    std::variant<std::string, int, double, bool, char, std::nullptr_t,
+                 std::shared_ptr<LBPLClass>, std::shared_ptr<LBPLInstance>,
+                 std::shared_ptr<LBPLCallable>>;
+#endif
diff --git a/lib/ast_printer.h b/lib/ast_printer.h
new file mode 100755 (executable)
index 0000000..01be7d3
--- /dev/null
@@ -0,0 +1,36 @@
+#ifndef AST_PRINTER_H
+#define AST_PRINTER_H
+
+#include "tree_nodes.h"
+#include "visitor.h"
+
+class AST_Printer : public Statement::Visitor, public Expression::Visitor {
+  void printLocation(Stmt *);
+
+  void visitFnStmt(FnStmt *) override;
+  void visitVarStmt(VarStmt *) override;
+  void visitClassStmt(ClassStmt *) override;
+  void visitIfStmt(IfStmt *) override;
+  void visitWhileStmt(WhileStmt *) override;
+  void visitForStmt(ForStmt *) override;
+  void visitScopedStmt(ScopedStmt *) override;
+  void visitExprStmt(ExprStmt *) override;
+  void visitReturnStmt(ReturnStmt *) override;
+
+  LBPLType visitBinaryExpr(BinaryExpr *) override;
+  LBPLType visitBreakExpr(BreakExpr *) override;
+  LBPLType visitContinueExpr(ContinueExpr *) override;
+  LBPLType visitUnaryExpr(UnaryExpr *) override;
+  LBPLType visitLiteralExpr(LiteralExpr *) override;
+  LBPLType visitGroupExpr(GroupingExpr *) override;
+  LBPLType visitSuperExpr(SuperExpr *) override;
+  LBPLType visitThisExpr(ThisExpr *) override;
+  LBPLType visitCallExpr(FnCallExpr *) override;
+  LBPLType visitGetFieldExpr(GetFieldExpr *) override;
+  LBPLType visitSetFieldExpr(SetFieldExpr *) override;
+  LBPLType visitTernaryExpr(TernaryExpr *) override;
+  LBPLType visitVarExpr(VariableExpr *) override;
+  LBPLType visitAssignExpr(AssignExpr *) override;
+};
+
+#endif
diff --git a/lib/builtin_methods.h b/lib/builtin_methods.h
new file mode 100644 (file)
index 0000000..b04c01a
--- /dev/null
@@ -0,0 +1,48 @@
+#ifndef BUILTIN_METHODS_H
+#define BUILTIN_METHODS_H
+
+#include "LBPLCallable.h"
+#include "statements.h"
+
+#include <chrono>
+#include <iostream>
+#include <variant>
+
+class LBPLPrintln : public LBPLCallable {
+public:
+  LBPLPrintln() {}
+
+  int arity() override { return 1; };
+
+  LBPLType call(Interpreter *, std::vector<LBPLType> &args) override {
+    if (std::holds_alternative<std::string>(args[0])) {
+      std::cout << std::get<std::string>(args[0]) << std::endl;
+    } else if (std::holds_alternative<int>(args[0])) {
+      std::cout << std::get<int>(args[0]) << std::endl;
+    } else if (std::holds_alternative<double>(args[0])) {
+      std::cout << std::get<double>(args[0]) << std::endl;
+    } else if (std::holds_alternative<char>(args[0])) {
+      std::cout << std::get<char>(args[0]) << std::endl;
+    } else if (std::holds_alternative<bool>(args[0])) {
+      std::cout << (std::get<bool>(args[0]) ? "true" : "false") << std::endl;
+    }
+
+    return nullptr;
+  }
+};
+
+class LBPLClock : public LBPLCallable {
+public:
+  LBPLClock() {}
+
+  int arity() override { return 0; };
+
+  LBPLType call(Interpreter *, std::vector<LBPLType> &args) override {
+    return std::chrono::duration_cast<std::chrono::milliseconds>(
+               std::chrono::system_clock::now().time_since_epoch())
+               .count() /
+           1000.0;
+  }
+};
+
+#endif
diff --git a/lib/common.h b/lib/common.h
new file mode 100755 (executable)
index 0000000..fbdc444
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef COMMON_H
+#define COMMON_H
+
+#include "token.h"
+#include "token_type.h"
+
+#include <string>
+#include <iostream>
+
+#endif
diff --git a/lib/environment.h b/lib/environment.h
new file mode 100755 (executable)
index 0000000..6fa8cce
--- /dev/null
@@ -0,0 +1,42 @@
+#ifndef ENVIRONMENT_H
+#define ENVIRONMENT_H
+
+#include "LBPLTypes.h"
+#include "token.h"
+#include "tree_nodes.h"
+
+#include <map>
+#include <memory>
+#include <string>
+#include <variant>
+
+class Environment {
+public:
+  std::map<std::string, LBPLType> env;
+  std::shared_ptr<Environment> enclosing;
+
+public:
+  Environment() : enclosing(nullptr) {}
+  Environment(Environment &other)
+      : env(other.env),
+        enclosing(other.enclosing ? other.enclosing->clone() : nullptr) {}
+  Environment(std::shared_ptr<Environment> &enclosing) : enclosing(enclosing) {}
+
+  std::shared_ptr<Environment> clone() {
+    return std::make_shared<Environment>(*this);
+  }
+
+  void define(const std::string &, LBPLType &);
+  void define(const std::string &, LBPLType &&);
+
+  void printEnv(const std::string &&);
+
+  LBPLType get(std::shared_ptr<const Token> &);
+  LBPLType getAt(int, std::shared_ptr<const Token> &);
+  LBPLType getAt(int, const std::string &);
+  void assign(std::shared_ptr<const Token> &, LBPLType &);
+  void assign(std::shared_ptr<const Token> &, LBPLType &&);
+  void assignAt(int, std::shared_ptr<const Token> &, LBPLType &);
+};
+
+#endif
diff --git a/lib/expressions.h b/lib/expressions.h
new file mode 100755 (executable)
index 0000000..3b7f324
--- /dev/null
@@ -0,0 +1,204 @@
+#ifndef EXPRESSIONS_H
+#define EXPRESSIONS_H
+
+#include "common.h"
+#include "visitor.h"
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+struct Expr {
+  std::string file;
+  int line, column;
+
+  Expr(int line, int column, const std::string &filename)
+      : line(line), column(column), file(filename) {}
+
+  virtual ~Expr(){};
+  virtual LBPLType accept(Expression::Visitor *) { return nullptr; }
+};
+
+struct BinaryExpr : public Expr {
+  std::unique_ptr<Expr> left;
+  std::unique_ptr<Expr> right;
+  std::shared_ptr<const Token> op;
+
+  BinaryExpr(int line, int column, const std::string &file,
+             std::unique_ptr<Expr> &left, std::unique_ptr<Expr> &right,
+             std::shared_ptr<const Token> &op)
+      : left(std::move(left)), right(std::move(right)), op(op),
+        Expr(line, column, file) {}
+  BinaryExpr(int line, int column, const std::string &file,
+             std::unique_ptr<Expr> &left, std::unique_ptr<Expr> &&right,
+             std::shared_ptr<const Token> &op)
+      : left(std::move(left)), right(std::move(right)), op(op),
+        Expr(line, column, file) {}
+
+  LBPLType accept(Expression::Visitor *visitor) {
+    return visitor->visitBinaryExpr(this);
+  }
+};
+
+struct BreakExpr : public Expr {
+  BreakExpr(int line, int column, const std::string &file)
+      : Expr(line, column, file) {}
+
+  LBPLType accept(Expression::Visitor *visitor) {
+    return visitor->visitBreakExpr(this);
+  }
+};
+
+struct ContinueExpr : public Expr {
+  ContinueExpr(int line, int column, const std::string &file)
+      : Expr(line, column, file) {}
+  LBPLType accept(Expression::Visitor *visitor) {
+    return visitor->visitContinueExpr(this);
+  }
+};
+
+struct UnaryExpr : public Expr {
+  std::unique_ptr<Expr> right;
+  std::shared_ptr<const Token> op;
+
+  UnaryExpr(int line, int column, const std::string &file,
+            std::unique_ptr<Expr> right, std::shared_ptr<const Token> &op)
+      : right(std::move(right)), op(op), Expr(line, column, file) {}
+  LBPLType accept(Expression::Visitor *visitor) {
+    return visitor->visitUnaryExpr(this);
+  }
+};
+
+struct LiteralExpr : public Expr {
+  std::shared_ptr<const Token> token;
+
+  LiteralExpr(int line, int column, const std::string &file,
+              std::shared_ptr<const Token> &literal)
+      : token(literal), Expr(line, column, file) {}
+  LiteralExpr(int line, int column, const std::string &file,
+              std::shared_ptr<const Token> &&literal)
+      : token(literal), Expr(line, column, file) {}
+  LBPLType accept(Expression::Visitor *visitor) {
+    return visitor->visitLiteralExpr(this);
+  }
+};
+
+struct SuperExpr : public Expr {
+  std::shared_ptr<const Token> field;
+
+  SuperExpr(int line, int column, const std::string &file,
+            std::shared_ptr<const Token> &field)
+      : field(field), Expr(line, column, file) {}
+  LBPLType accept(Expression::Visitor *visitor) {
+    return visitor->visitSuperExpr(this);
+  }
+};
+
+struct ThisExpr : public Expr {
+  ThisExpr(int line, int column, const std::string &file)
+      : Expr(line, column, file) {}
+  LBPLType accept(Expression::Visitor *visitor) {
+    return visitor->visitThisExpr(this);
+  }
+};
+
+struct GroupingExpr : public Expr {
+  std::unique_ptr<Expr> expr;
+
+  GroupingExpr(int line, int column, const std::string &file,
+               std::unique_ptr<Expr> &expr)
+      : expr(std::move(expr)), Expr(line, column, file) {}
+  LBPLType accept(Expression::Visitor *visitor) {
+    return visitor->visitGroupExpr(this);
+  }
+};
+
+struct VariableExpr : public Expr {
+  std::shared_ptr<const Token> variable;
+
+  VariableExpr(int line, int column, const std::string &file,
+               std::shared_ptr<const Token> &variable)
+      : variable(variable), Expr(line, column, file) {}
+  LBPLType accept(Expression::Visitor *visitor) {
+    return visitor->visitVarExpr(this);
+  }
+};
+
+struct AssignExpr : public Expr {
+  std::shared_ptr<const Token> variable;
+  std::unique_ptr<Expr> value;
+
+  AssignExpr(int line, int column, const std::string &file,
+             std::shared_ptr<const Token> &variable,
+             std::unique_ptr<Expr> &value)
+      : variable(variable), value(std::move(value)), Expr(line, column, file) {}
+
+  LBPLType accept(Expression::Visitor *visitor) {
+    return visitor->visitAssignExpr(this);
+  }
+};
+
+struct FnCallExpr : public Expr {
+  std::unique_ptr<Expr> callee;
+  std::vector<std::unique_ptr<Expr>> args;
+
+  FnCallExpr(int line, int column, const std::string &file,
+             std::unique_ptr<Expr> &callee,
+             std::vector<std::unique_ptr<Expr>> &args)
+      : callee(std::move(callee)), args(std::move(args)),
+        Expr(line, column, file) {}
+
+  LBPLType accept(Expression::Visitor *visitor) {
+    return visitor->visitCallExpr(this);
+  }
+};
+
+struct TernaryExpr : public Expr {
+  std::unique_ptr<Expr> condition;
+  std::unique_ptr<Expr> trueBranch;
+  std::unique_ptr<Expr> falseBranch;
+
+  TernaryExpr(int line, int column, const std::string &file,
+              std::unique_ptr<Expr> &condition,
+              std::unique_ptr<Expr> &trueBranch,
+              std::unique_ptr<Expr> &&falseBranch)
+      : condition(std::move(condition)), trueBranch(std::move(trueBranch)),
+        falseBranch(std::move(falseBranch)), Expr(line, column, file) {}
+
+  LBPLType accept(Expression::Visitor *visitor) {
+    return visitor->visitTernaryExpr(this);
+  }
+};
+
+struct GetFieldExpr : public Expr {
+  std::shared_ptr<const Token> field;
+  std::unique_ptr<Expr> instance;
+
+  GetFieldExpr(int line, int column, const std::string &file,
+               std::unique_ptr<Expr> &instance,
+               std::shared_ptr<const Token> &field)
+      : instance(std::move(instance)), field(field), Expr(line, column, file) {}
+
+  LBPLType accept(Expression::Visitor *visitor) {
+    return visitor->visitGetFieldExpr(this);
+  }
+};
+
+struct SetFieldExpr : public Expr {
+  std::shared_ptr<const Token> field;
+  std::unique_ptr<Expr> value;
+  std::unique_ptr<Expr> instance;
+
+  SetFieldExpr(int line, int column, const std::string &file,
+               std::unique_ptr<Expr> &instance,
+               std::shared_ptr<const Token> &field,
+               std::unique_ptr<Expr> &value)
+      : instance(std::move(instance)), field(field), value(std::move(value)),
+        Expr(line, column, file) {}
+
+  LBPLType accept(Expression::Visitor *visitor) {
+    return visitor->visitSetFieldExpr(this);
+  }
+};
+
+#endif
diff --git a/lib/interpreter.h b/lib/interpreter.h
new file mode 100644 (file)
index 0000000..13650aa
--- /dev/null
@@ -0,0 +1,80 @@
+#ifndef INTERPRETER_H
+#define INTERPRETER_H
+
+#include "LBPLTypes.h"
+#include "builtin_methods.h"
+#include "environment.h"
+#include "runtime_error.h"
+#include "visitor.h"
+
+#include <map>
+#include <memory>
+#include <vector>
+
+class BreakException {};
+class ContinueException {};
+class ReturnException {
+public:
+  LBPLType value;
+
+public:
+  ReturnException(LBPLType &&value) : value(value) {}
+};
+
+class Interpreter : Statement::Visitor, Expression::Visitor {
+private:
+  std::shared_ptr<Environment> global, currentEnv;
+  std::map<Expr *, int> locals;
+
+private:
+  void execute(std::unique_ptr<Stmt> &);
+
+  LBPLType evaluate(std::unique_ptr<Expr> &);
+  LBPLType lookupVariable(std::shared_ptr<const Token> &, Expr *);
+  bool isTruthy(const LBPLType &);
+  bool isTruthy(LBPLType &&);
+
+  LBPLType performBinaryOperation(std::shared_ptr<const Token> &,
+                                  const LBPLType &, const LBPLType &);
+
+  void visitFnStmt(FnStmt *) override;
+  void visitVarStmt(VarStmt *) override;
+  void visitClassStmt(ClassStmt *) override;
+  void visitIfStmt(IfStmt *) override;
+  void visitWhileStmt(WhileStmt *) override;
+  void visitForStmt(ForStmt *) override;
+  void visitScopedStmt(ScopedStmt *) override;
+  void visitExprStmt(ExprStmt *) override;
+  void visitReturnStmt(ReturnStmt *) override;
+
+  LBPLType visitBinaryExpr(BinaryExpr *) override;
+  LBPLType visitBreakExpr(BreakExpr *) override;
+  LBPLType visitContinueExpr(ContinueExpr *) override;
+  LBPLType visitUnaryExpr(UnaryExpr *) override;
+  LBPLType visitLiteralExpr(LiteralExpr *) override;
+  LBPLType visitGroupExpr(GroupingExpr *) override;
+  LBPLType visitSuperExpr(SuperExpr *) override;
+  LBPLType visitThisExpr(ThisExpr *) override;
+  LBPLType visitCallExpr(FnCallExpr *) override;
+  LBPLType visitGetFieldExpr(GetFieldExpr *) override;
+  LBPLType visitSetFieldExpr(SetFieldExpr *) override;
+  LBPLType visitTernaryExpr(TernaryExpr *) override;
+  LBPLType visitVarExpr(VariableExpr *) override;
+  LBPLType visitAssignExpr(AssignExpr *) override;
+
+public:
+  void executeBlock(std::vector<std::unique_ptr<Stmt>> &,
+                    std::shared_ptr<Environment> &);
+  void executeBlock(std::vector<std::unique_ptr<Stmt>> &,
+                    std::shared_ptr<Environment> &&);
+  void interpret(std::vector<std::unique_ptr<Stmt>> &);
+  void resolve(Expr *, int);
+
+  Interpreter()
+      : global(std::make_shared<Environment>()), currentEnv(global), locals() {
+    global->define("println", std::make_shared<LBPLPrintln>());
+    global->define("clock", std::make_shared<LBPLClock>());
+  }
+};
+
+#endif
diff --git a/lib/lexer.h b/lib/lexer.h
new file mode 100755 (executable)
index 0000000..7a3d68f
--- /dev/null
@@ -0,0 +1,56 @@
+#ifndef LEXER_H
+#define LEXER_H
+
+#include "common.h"
+
+#include <fstream>
+#include <iomanip>
+#include <memory>
+#include <vector>
+
+class Lexer {
+private:
+  int line;
+  std::ifstream &stream;
+  std::string filename;
+  std::string currentLine;
+  std::string::iterator start;
+  std::string::iterator current;
+
+public:
+  bool hadError;
+
+private:
+  void skipWhitespace();
+  void goToNextLine();
+
+  std::shared_ptr<const Token> makeToken(TokenType, std::string = "");
+  std::shared_ptr<const Token> makeNumberToken();
+  std::shared_ptr<const Token> makeIdentifierToken();
+  std::shared_ptr<const Token> makeErrorToken(std::string);
+
+  TokenType isIdentifierOrKeywork();
+  TokenType checkKeyword(int startIndex, const std::string &restOfKeyword,
+                         TokenType typeIfMatch);
+
+  char advance();
+  char peek() const;
+  char peekNext() const;
+
+  bool isAtEnd() const;
+  bool isAtLineEnd() const;
+  bool isDigit(char ch) const;
+  bool isAlpha(char ch) const;
+  bool match(char ch);
+
+public:
+  Lexer(std::ifstream &, const std::string &);
+
+  int getLine();
+  int getColumn();
+  std::string getFilename();
+
+  std::shared_ptr<const Token> getNextToken();
+};
+
+#endif
diff --git a/lib/main.h b/lib/main.h
new file mode 100755 (executable)
index 0000000..78d6836
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef MAIN_H
+#define MAIN_H
+
+#include "common.h"
+#include "interpreter.h"
+#include "parser.h"
+#include "resolver.h"
+#include "statements.h"
+
+#include "ast_printer.h"
+
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+
+std::string readFile(const char *);
+
+#endif
diff --git a/lib/parser.h b/lib/parser.h
new file mode 100755 (executable)
index 0000000..daa8b76
--- /dev/null
@@ -0,0 +1,82 @@
+#ifndef PARSER_H
+#define PARSER_H
+
+#include "expressions.h"
+#include "lexer.h"
+#include "statements.h"
+#include "syntax_error.h"
+
+#include <fstream>
+#include <memory>
+#include <unordered_set>
+#include <vector>
+
+#define CONSUME_SEMICOLON(keyword)                                             \
+  consume("Expected ';' after " + std::string(keyword) + " statement.",        \
+          TokenType::Semicolon);
+
+class Parser {
+private:
+  Lexer *lexer;
+  std::shared_ptr<const Token> current;
+  std::shared_ptr<const Token> previous;
+  std::unordered_set<std::string> importedFiles;
+
+public:
+  bool hadError;
+
+private:
+  bool isAtEnd();
+  void synchronize();
+  std::shared_ptr<const Token> advance();
+
+  template <typename... TokenTypes> bool check(const TokenTypes &...);
+  template <typename... TokenTypes>
+  std::shared_ptr<const Token> consume(const std::string &msg, const TokenTypes &...);
+  template <typename... TokenTypes> bool match(const TokenTypes &...);
+
+  std::vector<std::unique_ptr<Stmt>> importStmt();
+  std::unique_ptr<FnStmt> functionDecl(const std::string &);
+  std::unique_ptr<VarStmt> varDecl();
+  std::unique_ptr<ClassStmt> classDecl();
+
+  std::vector<std::unique_ptr<Stmt>> stmtSequence();
+
+  std::unique_ptr<Stmt> declaration();
+  std::unique_ptr<Stmt> statement();
+  std::unique_ptr<Stmt> ifStmt();
+  std::unique_ptr<Stmt> whileStmt();
+  std::unique_ptr<Stmt> loopStmt();
+  std::unique_ptr<Stmt> forStmt();
+  std::unique_ptr<Stmt> returnStmt();
+  std::unique_ptr<Stmt> scopedStmt();
+  std::unique_ptr<Stmt> expressionStmt();
+
+  std::unique_ptr<Expr> expression();
+  std::unique_ptr<Expr> assignment();
+  std::unique_ptr<Expr> orExpr();
+  std::unique_ptr<Expr> andExpr();
+  std::unique_ptr<Expr> equality();
+  std::unique_ptr<Expr> comparison();
+  std::unique_ptr<Expr> term();
+  std::unique_ptr<Expr> factor();
+  std::unique_ptr<Expr> unary();
+  std::unique_ptr<Expr> call();
+  std::unique_ptr<Expr> primary();
+
+public:
+  Parser(std::ifstream &file, std::string &filename)
+      : lexer(new Lexer(file, filename)), current(lexer->getNextToken()),
+        previous(current) {
+    importedFiles.insert(filename);
+  }
+
+  Parser(std::ifstream &file, const std::string &filename,
+         std::unordered_set<std::string> &importedFiles)
+      : importedFiles(importedFiles), lexer(new Lexer(file, filename)),
+        current(lexer->getNextToken()), previous(current) {}
+
+  std::vector<std::unique_ptr<Stmt>> parse();
+};
+
+#endif
diff --git a/lib/resolver.h b/lib/resolver.h
new file mode 100644 (file)
index 0000000..4783f6f
--- /dev/null
@@ -0,0 +1,91 @@
+#ifndef RESOLVER_H
+#define RESOLVER_H
+
+#include "environment.h"
+#include "interpreter.h"
+#include "statements.h"
+#include "syntax_error.h"
+#include "visitor.h"
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace FunctionType {
+enum Type {
+  None,
+  Function,
+  Initializer,
+};
+}
+
+namespace ClassType {
+enum Type {
+  None,
+  Subclass,
+};
+}
+
+enum VarState {
+  None,
+  Init,
+  Ready,
+};
+
+class Resolver : Statement::Visitor, Expression::Visitor {
+private:
+  Interpreter *interpreter;
+  FunctionType::Type currentFn;
+  ClassType::Type currentClass;
+  int loops;
+  std::vector<std::map<std::string, VarState>> scopes;
+
+public:
+  bool hadError;
+
+private:
+  void beginScope() { scopes.emplace_back(std::map<std::string, VarState>()); }
+  void endScope() { scopes.pop_back(); }
+
+  void declare(const Token *);
+  void define(const Token *);
+
+  void resolveLocal(Expr *, const std::string &);
+  void resolveLocal(Expr *, const Token *);
+  void resolveFunction(FnStmt *, FunctionType::Type);
+
+  void visitFnStmt(FnStmt *) override;
+  void visitVarStmt(VarStmt *) override;
+  void visitClassStmt(ClassStmt *) override;
+  void visitIfStmt(IfStmt *) override;
+  void visitWhileStmt(WhileStmt *) override;
+  void visitForStmt(ForStmt *) override;
+  void visitScopedStmt(ScopedStmt *) override;
+  void visitExprStmt(ExprStmt *) override;
+  void visitReturnStmt(ReturnStmt *) override;
+
+  LBPLType visitBinaryExpr(BinaryExpr *) override;
+  LBPLType visitBreakExpr(BreakExpr *) override;
+  LBPLType visitContinueExpr(ContinueExpr *) override;
+  LBPLType visitUnaryExpr(UnaryExpr *) override;
+  LBPLType visitLiteralExpr(LiteralExpr *) override;
+  LBPLType visitGroupExpr(GroupingExpr *) override;
+  LBPLType visitSuperExpr(SuperExpr *) override;
+  LBPLType visitThisExpr(ThisExpr *) override;
+  LBPLType visitCallExpr(FnCallExpr *) override;
+  LBPLType visitGetFieldExpr(GetFieldExpr *) override;
+  LBPLType visitSetFieldExpr(SetFieldExpr *) override;
+  LBPLType visitTernaryExpr(TernaryExpr *) override;
+  LBPLType visitVarExpr(VariableExpr *) override;
+  LBPLType visitAssignExpr(AssignExpr *) override;
+
+public:
+  Resolver(Interpreter *interpreter)
+      : interpreter(interpreter), currentFn(FunctionType::None),
+        currentClass(ClassType::None), loops(0), scopes() {}
+
+  void resolve(std::vector<std::unique_ptr<Stmt>> &);
+};
+
+#endif
diff --git a/lib/runtime_error.h b/lib/runtime_error.h
new file mode 100644 (file)
index 0000000..1d791de
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef RUNTIME_ERROR_H
+#define RUNTIME_ERROR_H
+
+#include "token.h"
+#include "expressions.h"
+#include "statements.h"
+
+#include <string>
+#include <optional>
+#include <iomanip>
+#include <sstream>
+
+struct RuntimeError {
+public:
+  int line;
+  int column;
+  std::string filename;
+  std::string msg;
+
+public:
+  RuntimeError(const Token *errToken, const std::string &msg)
+      : line(errToken->line), column(errToken->column), filename(errToken->filename), msg(msg) {}
+
+  RuntimeError(Expr *errToken, const std::string &msg)
+      : line(errToken->line), column(errToken->column), filename(errToken->file), msg(msg) {}
+
+  RuntimeError(Stmt *errToken, const std::string &msg)
+      : line(errToken->line), column(errToken->column), filename(errToken->filename), msg(msg) {}
+
+  std::string what();
+};
+
+#endif
diff --git a/lib/statements.h b/lib/statements.h
new file mode 100755 (executable)
index 0000000..992f489
--- /dev/null
@@ -0,0 +1,133 @@
+#ifndef STATEMENTS_H
+#define STATEMENTS_H
+
+#include "expressions.h"
+
+#include <memory>
+#include <optional>
+#include <utility>
+
+struct Stmt {
+  std::string filename;
+  int line, column;
+
+  Stmt(int line, int column, const std::string &filename)
+      : line(line), column(column), filename(filename) {}
+
+  virtual ~Stmt() {}
+  virtual void accept(Statement::Visitor *) = 0;
+};
+
+struct FnStmt : public Stmt {
+  std::shared_ptr<const Token> name;
+  std::vector<std::shared_ptr<const Token> > args;
+  std::vector<std::unique_ptr<Stmt>> body;
+
+  FnStmt(int line, int column, const std::string &file, std::shared_ptr<const Token> &name,
+         std::vector<std::shared_ptr<const Token>> &args, std::vector<std::unique_ptr<Stmt>> &&body)
+      : name(name), args(args), body(std::move(body)), Stmt(line, column, file) {}
+
+  void accept(Statement::Visitor *visitor) { visitor->visitFnStmt(this); }
+};
+
+struct VarStmt : public Stmt {
+  std::shared_ptr<const Token> name;
+  std::unique_ptr<Expr> value;
+
+  VarStmt(int line, int column, const std::string &file, std::shared_ptr<const Token> &name,
+          std::unique_ptr<Expr> &value)
+      : name(name), value(std::move(value)),
+        Stmt(line, column, file) {}
+
+  void accept(Statement::Visitor *visitor) { visitor->visitVarStmt(this); }
+};
+
+struct ClassStmt : public Stmt {
+  std::shared_ptr<const Token> name;
+  std::unique_ptr<VariableExpr> superclass;
+  std::vector<std::unique_ptr<Stmt>> body;
+
+  ClassStmt(int line, int column, const std::string &file, std::shared_ptr<const Token> &name,
+            std::unique_ptr<VariableExpr> &superclass,
+            std::vector<std::unique_ptr<Stmt>> &&body)
+      : name(name), superclass(std::move(superclass)), body(std::move(body)),
+        Stmt(line, column, file) {}
+
+  void accept(Statement::Visitor *visitor) { visitor->visitClassStmt(this); }
+};
+
+struct IfStmt : public Stmt {
+  std::unique_ptr<Expr> condition;
+  std::unique_ptr<Stmt> trueBranch;
+  std::unique_ptr<Stmt> falseBranch;
+
+  IfStmt(int line, int column, const std::string &file,
+         std::unique_ptr<Expr> &condition,
+         std::unique_ptr<Stmt> &trueBranch, std::unique_ptr<Stmt> &falseBranch)
+      : condition(std::move(condition)), trueBranch(std::move(trueBranch)),
+        falseBranch(std::move(falseBranch)), Stmt(line, column, file) {}
+
+  void accept(Statement::Visitor *visitor) { visitor->visitIfStmt(this); }
+};
+
+struct WhileStmt : public Stmt {
+  std::unique_ptr<Expr> condition;
+  std::unique_ptr<Stmt> body;
+
+  WhileStmt(int line, int column, const std::string &file,
+            std::unique_ptr<Expr> &cond, std::unique_ptr<Stmt> &body)
+      : condition(std::move(cond)), body(std::move(body)),
+        Stmt(line, column, file) {}
+
+  WhileStmt(int line, int column, const std::string &file,
+            std::unique_ptr<Expr> &cond, std::unique_ptr<Stmt> &&body)
+      : condition(std::move(cond)), body(std::move(body)),
+        Stmt(line, column, file) {}
+
+  void accept(Statement::Visitor *visitor) { visitor->visitWhileStmt(this); }
+};
+
+struct ForStmt : public Stmt {
+  std::unique_ptr<Expr> increment, condition;
+  std::unique_ptr<Stmt> initializer, body;
+
+  ForStmt(int line, int column, const std::string &file,
+          std::unique_ptr<Stmt> &initializer, std::unique_ptr<Expr> &cond,
+          std::unique_ptr<Expr> &increment, std::unique_ptr<Stmt> &body)
+      : initializer(std::move(initializer)), condition(std::move(cond)),
+        increment(std::move(increment)), body(std::move(body)),
+        Stmt(line, column, file) {}
+
+  void accept(Statement::Visitor *visitor) { visitor->visitForStmt(this); }
+};
+
+struct ScopedStmt : public Stmt {
+  std::vector<std::unique_ptr<Stmt>> body;
+
+  ScopedStmt(int line, int column, const std::string &file,
+             std::vector<std::unique_ptr<Stmt>> &&body)
+      : body(std::move(body)), Stmt(line, column, file) {}
+  void accept(Statement::Visitor *visitor) { visitor->visitScopedStmt(this); }
+};
+
+struct ExprStmt : public Stmt {
+  std::unique_ptr<Expr> expr;
+
+  ExprStmt(int line, int column, const std::string &file,
+           std::unique_ptr<Expr> &&expr)
+      : expr(std::move(expr)), Stmt(line, column, file) {}
+
+  void accept(Statement::Visitor *visitor) { visitor->visitExprStmt(this); }
+};
+
+struct ReturnStmt : public Stmt {
+  std::unique_ptr<Expr> value;
+
+  ReturnStmt(int line, int column, const std::string &file,
+             std::unique_ptr<Expr> &value)
+      : value(std::move(value)), Stmt(line, column, file) {}
+
+  void accept(Statement::Visitor *visitor) { visitor->visitReturnStmt(this); }
+};
+
+#endif
diff --git a/lib/syntax_error.h b/lib/syntax_error.h
new file mode 100755 (executable)
index 0000000..48c17c8
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef SYNTAX_ERROR_H
+#define SYNTAX_ERROR_H
+
+#include "token.h"
+#include "expressions.h"
+#include "statements.h"
+
+#include <string>
+#include <optional>
+#include <iomanip>
+#include <sstream>
+
+struct SyntaxError {
+public:
+  int line;
+  int column;
+  std::string filename;
+  std::string msg;
+
+public:
+  SyntaxError(const Token *errToken, const std::string &msg)
+      : line(errToken->line), column(errToken->column), filename(errToken->filename), msg(msg) {}
+
+  SyntaxError(Expr *errExpr, const std::string &msg)
+      : line(errExpr->line), column(errExpr->column), filename(errExpr->file), msg(msg) {}
+
+  SyntaxError(Stmt *errStmt, const std::string &msg)
+      : line(errStmt->line), column(errStmt->column), filename(errStmt->filename), msg(msg) {}
+
+  std::string what();
+};
+
+#endif
diff --git a/lib/token.h b/lib/token.h
new file mode 100755 (executable)
index 0000000..47c4be1
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef TOKEN_H
+#define TOKEN_H
+
+#include "token_type.h"
+
+#include <string>
+
+class Token {
+public:
+  int line;
+  int column;
+  TokenType type;
+  std::string lexeme;
+  std::string filename;
+
+  Token(TokenType type, const std::string &lexeme, int line, int column, const std::string &filename)
+      : type(type), lexeme(lexeme), line(line), column(column), filename(filename) {}
+};
+
+#endif
diff --git a/lib/token_type.h b/lib/token_type.h
new file mode 100755 (executable)
index 0000000..b30e872
--- /dev/null
@@ -0,0 +1,64 @@
+#ifndef TOKEN_TYPE_H
+#define TOKEN_TYPE_H
+
+enum TokenType {
+  // Single-character tokens.
+  LeftParen,
+  RightParen,
+  LeftBrace,
+  RightBrace,
+  Question,
+  Comma,
+  Dot,
+  Colon,
+  Semicolon,
+  ModOp,
+  Minus,
+  Plus,
+  Slash,
+  Star,
+
+  // One or two character tokens.
+  Bang,
+  BangEqual,
+  Equal,
+  EqualEqual,
+  ShiftRight,
+  Greater,
+  GreaterEqual,
+  ShiftLeft,
+  Less,
+  LessEqual,
+
+  // Literals.
+  Identifier,
+  String,
+  Char,
+  Number,
+
+  // Keywords.
+  Import,
+  Let,
+  And,
+  Class,
+  Else,
+  False,
+  Fn,
+  For,
+  If,
+  Nil,
+  Or,
+  Return,
+  Break,
+  Continue,
+  Super,
+  This,
+  True,
+  While,
+  Loop,
+  Main,
+  Eof,
+  Error,
+};
+
+#endif
diff --git a/lib/tree_nodes.h b/lib/tree_nodes.h
new file mode 100644 (file)
index 0000000..214c372
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef TREE_NODES_H
+#define TREE_NODES_H
+
+class Stmt;
+class FnStmt;
+class VarStmt;
+class ClassStmt;
+class IfStmt;
+class WhileStmt;
+class ForStmt;
+class ScopedStmt;
+class ExprStmt;
+class ReturnStmt;
+
+class Expr;
+class BinaryExpr;
+class BreakExpr;
+class ContinueExpr;
+class UnaryExpr;
+class LiteralExpr;
+class GroupingExpr;
+class SuperExpr;
+class ThisExpr;
+class FnCallExpr;
+class GetFieldExpr;
+class SetFieldExpr;
+class TernaryExpr;
+class VariableExpr;
+class AssignExpr;
+
+#endif
diff --git a/lib/visitor.h b/lib/visitor.h
new file mode 100755 (executable)
index 0000000..37f75d8
--- /dev/null
@@ -0,0 +1,42 @@
+#ifndef VISITOR_H
+#define VISITOR_H
+
+#include "LBPLTypes.h"
+#include "tree_nodes.h"
+
+#include <iostream>
+
+namespace Statement {
+struct Visitor {
+  virtual void visitFnStmt(FnStmt *) = 0;
+  virtual void visitVarStmt(VarStmt *) = 0;
+  virtual void visitClassStmt(ClassStmt *) = 0;
+  virtual void visitIfStmt(IfStmt *) = 0;
+  virtual void visitWhileStmt(WhileStmt *) = 0;
+  virtual void visitForStmt(ForStmt *) = 0;
+  virtual void visitScopedStmt(ScopedStmt *) = 0;
+  virtual void visitExprStmt(ExprStmt *) = 0;
+  virtual void visitReturnStmt(ReturnStmt *) = 0;
+};
+} // namespace Statement
+
+namespace Expression {
+struct Visitor {
+  virtual LBPLType visitBinaryExpr(BinaryExpr *) = 0;
+  virtual LBPLType visitBreakExpr(BreakExpr *) = 0;
+  virtual LBPLType visitContinueExpr(ContinueExpr *) = 0;
+  virtual LBPLType visitUnaryExpr(UnaryExpr *) = 0;
+  virtual LBPLType visitLiteralExpr(LiteralExpr *) = 0;
+  virtual LBPLType visitGroupExpr(GroupingExpr *) = 0;
+  virtual LBPLType visitSuperExpr(SuperExpr *) = 0;
+  virtual LBPLType visitThisExpr(ThisExpr *) = 0;
+  virtual LBPLType visitCallExpr(FnCallExpr *) = 0;
+  virtual LBPLType visitGetFieldExpr(GetFieldExpr *) = 0;
+  virtual LBPLType visitSetFieldExpr(SetFieldExpr *) = 0;
+  virtual LBPLType visitTernaryExpr(TernaryExpr *) = 0;
+  virtual LBPLType visitVarExpr(VariableExpr *) = 0;
+  virtual LBPLType visitAssignExpr(AssignExpr *) = 0;
+};
+} // namespace Expression
+
+#endif
diff --git a/src/LBPLClass.cpp b/src/LBPLClass.cpp
new file mode 100644 (file)
index 0000000..ba83e5f
--- /dev/null
@@ -0,0 +1,30 @@
+#include "LBPLClass.h"
+#include "LBPLInstance.h"
+#include "interpreter.h"
+
+LBPLType LBPLClass::call(Interpreter *interpreter, std::vector<LBPLType> &args) {
+    auto instance = std::make_shared<LBPLInstance>(this);
+
+    LBPLFunc *init = findMethod("init");
+    if (init) {
+      init->bind(instance);
+      init->call(interpreter, args);
+    }
+
+    return instance;
+}
+
+LBPLFunc *LBPLClass::findMethod(const std::string &name) {
+    if (methods.contains(name)) {
+      return methods.find(name)->second;
+    } else if (superclass) {
+      return superclass->findMethod(name);
+    }
+
+    return nullptr;
+}
+
+int LBPLClass::arity() {
+    auto it = methods.find("init");
+    return it == methods.end() ? 0 : it->second->arity();
+}
diff --git a/src/LBPLFunction.cpp b/src/LBPLFunction.cpp
new file mode 100644 (file)
index 0000000..4d95259
--- /dev/null
@@ -0,0 +1,30 @@
+#include "LBPLFunction.h"
+#include "interpreter.h"
+
+void LBPLFunc::bind(std::shared_ptr<LBPLInstance> &&instance) {
+  closureEnv = std::make_shared<Environment>();
+  closureEnv->define("this", instance);
+}
+
+void LBPLFunc::bind(std::shared_ptr<LBPLInstance> &instance) {
+  closureEnv = std::make_shared<Environment>();
+  closureEnv->define("this", instance);
+}
+
+int LBPLFunc::arity() { return stmt->args.size(); }
+
+LBPLType LBPLFunc::call(Interpreter *interpreter, std::vector<LBPLType> &args) {
+  auto env = std::make_shared<Environment>(closureEnv);
+
+  for (int i = 0; i < stmt->args.size(); i++) {
+    env->define(stmt->args[i]->lexeme, args[i]);
+  }
+
+  try {
+    interpreter->executeBlock(stmt->body, env);
+  } catch (ReturnException &ret) {
+    return isInitializer ? closureEnv->getAt(0, "this") : ret.value;
+  }
+
+  return isInitializer ? closureEnv->getAt(0, "this") : nullptr;
+}
diff --git a/src/LBPLInstance.cpp b/src/LBPLInstance.cpp
new file mode 100644 (file)
index 0000000..66df9f8
--- /dev/null
@@ -0,0 +1,19 @@
+#include "LBPLInstance.h"
+
+LBPLType LBPLInstance::get(const Token *name) {
+  if (fields.contains(name->lexeme)) {
+    return fields.find(name->lexeme)->second;
+  }
+
+  LBPLFunc *method = lbplClass->findMethod(name->lexeme);
+  if (method) {
+    method->bind(std::make_shared<LBPLInstance>(this));
+    return std::make_shared<LBPLFunc>(*method);
+  }
+
+  throw RuntimeError(name, "Undefined field '" + name->lexeme + "'.");
+}
+
+void LBPLInstance::set(const Token *name, LBPLType &value) {
+  fields.insert_or_assign(name->lexeme, value);
+}
diff --git a/src/ast_printer.cpp b/src/ast_printer.cpp
new file mode 100755 (executable)
index 0000000..2fbb2c0
--- /dev/null
@@ -0,0 +1,215 @@
+#include "ast_printer.h"
+#include "statements.h"
+
+#include <iostream>
+
+void AST_Printer::printLocation(Stmt *stmt) {
+  std::cout << "Location: " << stmt->filename << " [Line: " << stmt->line
+            << ", Column: " << stmt->column << "]\n";
+}
+
+void AST_Printer::visitFnStmt(FnStmt *stmt) {
+  printLocation(stmt);
+  std::cout << "(";
+  std::cout << "fn `" + stmt->name->lexeme + "` args[";
+
+  for (auto &&arg : stmt->args) {
+    std::cout << "(" << arg->lexeme << ")";
+  }
+
+  std::cout << "] ` body {\n";
+  for (auto &&bodyStmt : stmt->body) {
+    bodyStmt->accept(this);
+  }
+
+  std::cout << "})\n";
+}
+
+void AST_Printer::visitVarStmt(VarStmt *var) {
+  printLocation(var);
+  std::cout << "(var " << var->name->lexeme << " value `";
+
+  if (var->value) {
+    var->value->accept(this);
+  } else {
+    std::cout << "nil";
+  }
+  std::cout << "`)\n";
+}
+
+void AST_Printer::visitClassStmt(ClassStmt *stmt) {
+  printLocation(stmt);
+  std::cout << "(class `" << stmt->name->lexeme << "` implements(";
+  if (stmt->superclass) {
+    stmt->superclass->accept(this);
+  }
+  std::cout << ") body {\n";
+  for (auto &&fn : stmt->body) {
+    fn->accept(this);
+  }
+  std::cout << "})\n";
+}
+
+void AST_Printer::visitIfStmt(IfStmt *fn) {
+  printLocation(fn);
+  std::cout << "(condition `";
+  fn->condition->accept(this);
+  std::cout << "` if true ";
+  fn->trueBranch->accept(this);
+  std::cout << " if false ";
+  if (fn->falseBranch) {
+    fn->falseBranch->accept(this);
+  }
+  std::cout << ")\n";
+}
+
+void AST_Printer::visitWhileStmt(WhileStmt *stmt) {
+  printLocation(stmt);
+  std::cout << "(while ";
+  stmt->condition->accept(this);
+  std::cout << " do ";
+  stmt->body->accept(this);
+  std::cout << ")\n";
+}
+
+void AST_Printer::visitForStmt(ForStmt *stmt) {
+  printLocation(stmt);
+  std::cout << "(for ";
+  if (stmt->initializer) {
+    stmt->initializer->accept(this);
+  }
+  std::cout << " while ";
+  if (stmt->condition) {
+    stmt->condition->accept(this);
+  } else {
+    std::cout << "yes";
+  }
+  std::cout << " do ";
+  stmt->body->accept(this);
+  std::cout << " then ";
+  if (stmt->increment) {
+    stmt->increment->accept(this);
+  }
+  std::cout << ")\n";
+}
+
+void AST_Printer::visitScopedStmt(ScopedStmt *stmt) {
+  printLocation(stmt);
+  std::cout << "{";
+  for (auto &&stmt : stmt->body) {
+    stmt->accept(this);
+  }
+  std::cout << "}";
+}
+
+void AST_Printer::visitExprStmt(ExprStmt *stmt) {
+  printLocation(stmt);
+  stmt->expr->accept(this);
+  std::cout << "\n";
+}
+
+void AST_Printer::visitReturnStmt(ReturnStmt *stmt) {
+  printLocation(stmt);
+  std::cout << "(return ";
+  if (stmt->value) {
+    stmt->value->accept(this);
+  } else {
+    std::cout << "void";
+  }
+  std::cout << ")\n";
+}
+
+LBPLType AST_Printer::visitBinaryExpr(BinaryExpr *expr) {
+  std::cout << "(" << expr->op->lexeme << " ";
+  expr->left->accept(this);
+  std::cout << " ";
+  expr->right->accept(this);
+  std::cout << ")";
+  return nullptr;
+}
+
+LBPLType AST_Printer::visitBreakExpr(BreakExpr *expr) {
+  std::cout << "(breaking)";
+  return nullptr;
+}
+
+LBPLType AST_Printer::visitContinueExpr(ContinueExpr *expr) {
+  std::cout << "(continuing)\n";
+  return nullptr;
+}
+
+LBPLType AST_Printer::visitUnaryExpr(UnaryExpr *expr) {
+  std::cout << "(" << expr->op->lexeme << " ";
+  expr->right->accept(this);
+  std::cout << ")";
+  return nullptr;
+}
+
+LBPLType AST_Printer::visitLiteralExpr(LiteralExpr *expr) {
+  std::cout << expr->token->lexeme;
+  return nullptr;
+}
+
+LBPLType AST_Printer::visitSuperExpr(SuperExpr *expr) {
+  std::cout << "(super)";
+  return nullptr;
+}
+
+LBPLType AST_Printer::visitThisExpr(ThisExpr *expr) {
+  std::cout << "(this)";
+  return nullptr;
+}
+
+LBPLType AST_Printer::visitAssignExpr(AssignExpr *expr) {
+  std::cout << "(assign ";
+  expr->value->accept(this);
+  std::cout << " to " << expr->variable->lexeme << ")";
+  return nullptr;
+}
+
+LBPLType AST_Printer::visitGroupExpr(GroupingExpr *expr) {
+  expr->expr->accept(this);
+  return nullptr;
+}
+
+LBPLType AST_Printer::visitCallExpr(FnCallExpr *expr) {
+  std::cout << "(calling `";
+  expr->callee->accept(this);
+  std::cout << "` with parameters [";
+
+  for (auto &&arg : expr->args) {
+    arg->accept(this);
+  }
+
+  std::cout << "])";
+  return nullptr;
+}
+
+LBPLType AST_Printer::visitGetFieldExpr(GetFieldExpr *expr) {
+  std::cout << "get field `" << expr->field->lexeme << "` from ";
+  expr->instance->accept(this);
+  return nullptr;
+}
+
+LBPLType AST_Printer::visitSetFieldExpr(SetFieldExpr *expr) {
+  std::cout << "setting field `" << expr->field->lexeme << "` from ";
+  expr->instance->accept(this);
+  std::cout << " to ";
+  expr->value->accept(this);
+  return nullptr;
+}
+
+LBPLType AST_Printer::visitTernaryExpr(TernaryExpr *expr) {
+  std::cout << "condition `";
+  expr->condition->accept(this);
+  std::cout << "` if true ";
+  expr->trueBranch->accept(this);
+  std::cout << " if false ";
+  expr->falseBranch->accept(this);
+  return nullptr;
+}
+
+LBPLType AST_Printer::visitVarExpr(VariableExpr *expr) {
+  std::cout << expr->variable->lexeme;
+  return nullptr;
+}
diff --git a/src/environment.cpp b/src/environment.cpp
new file mode 100755 (executable)
index 0000000..ef9a29a
--- /dev/null
@@ -0,0 +1,89 @@
+#include "environment.h"
+#include "LBPLTypes.h"
+#include "runtime_error.h"
+#include <memory>
+#include <string>
+#include <utility>
+#include <variant>
+
+void Environment::define(const std::string &name, LBPLType &value) {
+  env.insert(std::make_pair(name, value));
+}
+void Environment::define(const std::string &name, LBPLType &&value) {
+  env.insert(std::make_pair(name, value));
+}
+
+
+LBPLType Environment::get(std::shared_ptr<const Token> &name) {
+  auto it = env.find(name->lexeme);
+
+  if (it != env.end()) {
+    return it->second;
+  }
+  if (enclosing) {
+    return enclosing->get(name);
+  }
+
+  throw RuntimeError(name.get(), "Undefined name '"+name->lexeme+"'.");
+}
+
+LBPLType Environment::getAt(int depth, std::shared_ptr<const Token> &name) {
+  return getAt(depth, name->lexeme);
+}
+
+LBPLType Environment::getAt(int depth, const std::string &name) {
+  if (depth > 0) {
+    return enclosing->getAt(depth-1, name);
+  }
+
+  auto it = env.find(name);
+  return it != env.end() ? it->second : nullptr;
+}
+
+void Environment::assign(std::shared_ptr<const Token> &name, LBPLType &value) {
+  if (env.contains(name->lexeme)) {
+    env.insert_or_assign(name->lexeme, value);
+  } else if (enclosing) {
+    enclosing->assign(name, value);
+  } else {
+    throw RuntimeError(name.get(), "Undefined variable '"+name->lexeme+"'.");
+  }
+}
+
+void Environment::assign(std::shared_ptr<const Token> &name, LBPLType &&value) {
+  assign(name, value);
+}
+
+void Environment::assignAt(int depth, std::shared_ptr<const Token> &name, LBPLType &value) {
+  if (depth > 0) {
+    enclosing->assignAt(depth-1, name, value);
+  }
+
+  env.insert_or_assign(name->lexeme, value);
+}
+
+void Environment::printEnv(const std::string &&msg) {
+  std::cout << "========"<< msg <<"=========" << std::endl;
+  for (const auto& [key, value] : env) {
+    std::cout << "\t" << key << ": ";
+
+    if (std::holds_alternative<int>(value)) {
+      std::cout << "int" << std::endl;
+    } else if (std::holds_alternative<double>(value)) {
+      std::cout << "double" << std::endl;
+    } else if (std::holds_alternative<char>(value)) {
+      std::cout << "char" << std::endl;
+    } else if (std::holds_alternative<std::nullptr_t>(value)) {
+      std::cout << "nullptr" << std::endl;
+    } else if (std::holds_alternative<std::shared_ptr<LBPLCallable>>(value)) {
+      std::cout << "callable" << std::endl;
+    } else if (std::holds_alternative<std::shared_ptr<LBPLClass>>(value)) {
+      std::cout << "class" << std::endl;
+    } else if (std::holds_alternative<std::shared_ptr<LBPLInstance>>(value)) {
+      std::cout << "instance" << std::endl;
+    } else {
+      std::cout << "idk" << std::endl;
+    }
+  }
+  std::cout << "===================================" << std::endl;
+}
diff --git a/src/interpreter.cpp b/src/interpreter.cpp
new file mode 100644 (file)
index 0000000..42432bd
--- /dev/null
@@ -0,0 +1,557 @@
+#include "interpreter.h"
+#include "LBPLClass.h"
+#include "LBPLFunction.h"
+#include "LBPLInstance.h"
+#include "LBPLTypes.h"
+#include "runtime_error.h"
+
+#include <chrono>
+#include <cstddef>
+#include <map>
+#include <memory>
+#include <string>
+#include <variant>
+class Timer {
+  std::chrono::time_point<std::chrono::high_resolution_clock> m_startTimepoint;
+
+public:
+  Timer() { m_startTimepoint = std::chrono::high_resolution_clock::now(); }
+
+  ~Timer() { stop(); }
+
+  void stop() {
+    auto endTimepoint = std::chrono::high_resolution_clock::now();
+
+    auto start = std::chrono::time_point_cast<std::chrono::microseconds>(
+                     m_startTimepoint)
+                     .time_since_epoch()
+                     .count();
+    auto end =
+        std::chrono::time_point_cast<std::chrono::microseconds>(endTimepoint)
+            .time_since_epoch()
+            .count();
+
+    auto duration = end - start;
+    auto ms = duration * 0.001;
+
+    std::cout << duration << "µs (" << ms << "ms)\n";
+  }
+};
+
+void Interpreter::interpret(std::vector<std::unique_ptr<Stmt>> &stmts) {
+  std::cout << "\tinterpreter begin\n";
+  try {
+    for (auto &&stmt : stmts) {
+      stmt->accept(this);
+    }
+  } catch (RuntimeError &e) {
+    std::cout << e.what();
+  }
+}
+
+void Interpreter::resolve(Expr *expr, int depth) {
+  locals.insert_or_assign(expr, depth);
+}
+
+void Interpreter::visitFnStmt(FnStmt *stmt) {
+  currentEnv->define(stmt->name->lexeme,
+                     std::make_shared<LBPLFunc>(stmt, currentEnv, false));
+}
+
+void Interpreter::visitVarStmt(VarStmt *stmt) {
+  LBPLType value;
+  if (stmt->value) {
+    value = stmt->value->accept(this);
+  }
+
+  currentEnv->define(stmt->name->lexeme, value);
+}
+
+void Interpreter::visitClassStmt(ClassStmt *stmt) {
+  LBPLType superclass;
+  currentEnv->define(stmt->name->lexeme, nullptr);
+
+  if (stmt->superclass) {
+    superclass = stmt->superclass->accept(this);
+    if (!std::holds_alternative<std::shared_ptr<LBPLClass>>(superclass)) {
+      throw RuntimeError(stmt->superclass.get(),
+                         "Superclass must be another class.");
+    }
+
+    currentEnv = std::make_shared<Environment>(currentEnv);
+    currentEnv->define("super",
+                       std::get<std::shared_ptr<LBPLClass>>(superclass));
+  }
+
+  std::map<std::string, LBPLFunc *> methods;
+  for (auto &&methodStmt : stmt->body) {
+    auto method = dynamic_cast<FnStmt *>(methodStmt.get());
+    if (!method) {
+      throw RuntimeError(methodStmt.get(), "Excepted class method.");
+    }
+
+    auto fn = new LBPLFunc(method, currentEnv, method->name->lexeme == "init");
+    methods.insert(std::make_pair(method->name->lexeme, fn));
+  }
+
+  if (stmt->superclass) {
+    currentEnv = currentEnv->enclosing;
+    currentEnv->assign(stmt->name,
+                       std::make_shared<LBPLClass>(
+                           stmt->name->lexeme,
+                           std::get<std::shared_ptr<LBPLClass>>(superclass),
+                           methods));
+  } else {
+    currentEnv->assign(
+        stmt->name, std::make_shared<LBPLClass>(stmt->name->lexeme, methods));
+  }
+}
+
+void Interpreter::visitIfStmt(IfStmt *stmt) {
+  if (isTruthy(stmt->condition->accept(this))) {
+    stmt->trueBranch->accept(this);
+  } else if (stmt->falseBranch) {
+    stmt->falseBranch->accept(this);
+  }
+}
+
+void Interpreter::visitWhileStmt(WhileStmt *stmt) {
+  try {
+    while (isTruthy(stmt->condition->accept(this))) {
+      try {
+        stmt->body->accept(this);
+      } catch (ContinueException &e) {
+      }
+    }
+  } catch (BreakException &e) {
+  }
+}
+
+void Interpreter::visitForStmt(ForStmt *stmt) {
+  auto env = currentEnv;
+  currentEnv = std::make_shared<Environment>(currentEnv);
+
+  stmt->initializer->accept(this);
+
+  try {
+    while (isTruthy(stmt->condition->accept(this))) {
+      try {
+        stmt->body->accept(this);
+      } catch (ContinueException &e) {
+      }
+
+      stmt->increment->accept(this);
+    }
+  } catch (BreakException &e) {
+  }
+
+  currentEnv = env;
+}
+
+void Interpreter::visitScopedStmt(ScopedStmt *stmt) {
+  executeBlock(stmt->body, std::make_shared<Environment>(currentEnv));
+}
+
+void Interpreter::visitExprStmt(ExprStmt *stmt) { stmt->expr->accept(this); }
+void Interpreter::visitReturnStmt(ReturnStmt *stmt) {
+  throw ReturnException(stmt->value->accept(this));
+}
+
+LBPLType Interpreter::visitBinaryExpr(BinaryExpr *expr) {
+  LBPLType left = expr->left->accept(this);
+  LBPLType right = expr->right->accept(this);
+  return performBinaryOperation(expr->op, left, right);
+
+  /* if (std::holds_alternative<int>(left) &&
+   * std::holds_alternative<int>(right)) { */
+  /*   return performBinaryOperation(expr->op.get(), std::get<int>(left), */
+  /*                                 std::get<int>(right)); */
+  /* } else if (std::holds_alternative<double>(left) && */
+  /*            std::holds_alternative<double>(right)) { */
+  /*   return performBinaryOperation(expr->op.get(), std::get<double>(left), */
+  /*                                 std::get<double>(right)); */
+  /* } else if (std::holds_alternative<double>(left) && */
+  /*            std::holds_alternative<int>(right)) { */
+  /*   return performBinaryOperation(expr->op.get(), std::get<double>(left), */
+  /*                                 std::get<int>(right)); */
+  /* } else if (std::holds_alternative<int>(left) && */
+  /*            std::holds_alternative<double>(right)) { */
+  /*   return performBinaryOperation(expr->op.get(), std::get<int>(left), */
+  /*                                 std::get<double>(right)); */
+  /* } else if ((std::holds_alternative<std::string>(left) || */
+  /*             std::holds_alternative<std::string>(right)) && */
+  /*            expr->op->type == TokenType::Plus) { */
+  /*   if (std::holds_alternative<std::string>(left) && */
+  /*       std::holds_alternative<std::string>(right)) { */
+  /*     return std::get<std::string>(left) + */
+  /*                     std::get<std::string>(right); */
+  /*   } else if (std::holds_alternative<std::string>(left) && */
+  /*              std::holds_alternative<int>(right)) { */
+  /*     return std::get<std::string>(left) + */
+  /*                     std::to_string(std::get<int>(right)); */
+  /*   } else if (std::holds_alternative<int>(left) && */
+  /*              std::holds_alternative<std::string>(right)) { */
+  /*     return std::get<std::string>(left) + */
+  /*                     std::to_string(std::get<double>(right)); */
+  /*   } else if (std::holds_alternative<double>(left) && */
+  /*              std::holds_alternative<std::string>(right)) { */
+  /*     return std::to_string(std::get<double>(left)) + */
+  /*                     std::get<std::string>(right); */
+  /*   } else if (std::holds_alternative<std::string>(left) && */
+  /*              std::holds_alternative<char>(right)) { */
+  /*     return std::get<std::string>(left) + std::get<char>(right); */
+  /*   } else if (std::holds_alternative<char>(left) && */
+  /*              std::holds_alternative<std::string>(right)) { */
+  /*     return std::get<char>(left) + std::get<std::string>(right); */
+  /*   } */
+  /* } */
+
+  /* throw RuntimeError(expr->op.get(), "Invalid binary operation operand."); */
+}
+
+LBPLType Interpreter::visitBreakExpr(BreakExpr *) { throw BreakException(); }
+LBPLType Interpreter::visitContinueExpr(ContinueExpr *) {
+  throw ContinueException();
+}
+
+LBPLType Interpreter::visitUnaryExpr(UnaryExpr *expr) {
+  LBPLType right = expr->right->accept(this);
+
+  if (expr->op->type == TokenType::Minus) {
+    if (std::holds_alternative<int>(right)) {
+      return -std::get<int>(right);
+    } else if (std::holds_alternative<double>(right)) {
+      return -std::get<double>(right);
+    }
+  } else if (expr->op->type == TokenType::Bang) {
+    return !isTruthy(right);
+  }
+
+  return nullptr;
+}
+
+LBPLType Interpreter::visitLiteralExpr(LiteralExpr *expr) {
+  if (expr->token->type == TokenType::True) {
+    return true;
+  } else if (expr->token->type == TokenType::False) {
+    return false;
+  } else if (expr->token->type == TokenType::Nil) {
+    return nullptr;
+  } else if (expr->token->type == TokenType::Char) {
+    return expr->token->lexeme[0];
+  } else if (expr->token->type == TokenType::String) {
+    return expr->token->lexeme;
+  } else if (expr->token->type == TokenType::Number) {
+    if (expr->token->lexeme.find('.') == std::string::npos) {
+      return std::stoi(expr->token->lexeme);
+    }
+    return std::stod(expr->token->lexeme);
+  } else if (expr->token->type == TokenType::Identifier) {
+    return lookupVariable(expr->token, expr);
+  }
+
+  return nullptr;
+}
+
+LBPLType Interpreter::visitGroupExpr(GroupingExpr *expr) {
+  return expr->expr->accept(this);
+}
+
+LBPLType Interpreter::visitSuperExpr(SuperExpr *) { return nullptr; }
+LBPLType Interpreter::visitThisExpr(ThisExpr *) { return nullptr; }
+LBPLType Interpreter::visitCallExpr(FnCallExpr *expr) {
+  LBPLType callee = expr->callee->accept(this);
+
+  std::vector<LBPLType> args;
+  args.reserve(expr->args.size());
+
+  for (auto &&arg : expr->args) {
+    args.emplace_back(arg->accept(this));
+  }
+
+  if (std::holds_alternative<std::shared_ptr<LBPLCallable>>(callee)) {
+    auto function = std::get<std::shared_ptr<LBPLCallable>>(callee);
+    if (function->arity() != args.size()) {
+      throw RuntimeError(expr->callee.get(), "Wrong number of arguments.");
+    }
+
+    return function->call(this, args);
+  } else if (std::holds_alternative<std::shared_ptr<LBPLClass>>(callee)) {
+    auto clas = std::get<std::shared_ptr<LBPLClass>>(callee);
+    if (clas->arity() != args.size()) {
+      throw RuntimeError(expr->callee.get(), "Wrong number of arguments.");
+    }
+
+    return clas->call(this, args);
+  }
+
+  throw RuntimeError(expr->callee.get(),
+                     "Can only call a function or class initializer.");
+}
+
+LBPLType Interpreter::visitGetFieldExpr(GetFieldExpr *expr) {
+  LBPLType instance = expr->instance->accept(this);
+  if (std::holds_alternative<std::shared_ptr<LBPLInstance>>(instance)) {
+    return std::get<std::shared_ptr<LBPLInstance>>(instance)->get(expr->field.get());
+  }
+
+  throw RuntimeError(expr->instance.get(), "Only instances of classes can have properties");
+}
+
+LBPLType Interpreter::visitSetFieldExpr(SetFieldExpr *expr) {
+  LBPLType instance = expr->instance->accept(this);
+
+  if (std::holds_alternative<std::shared_ptr<LBPLInstance>>(instance)) {
+    LBPLType value = expr->value->accept(this);
+    std::get<std::shared_ptr<LBPLInstance>>(instance)->set(expr->field.get(), value);
+  } else {
+    throw RuntimeError(expr->instance.get(), "Only instances of classes can have properties");
+  }
+
+  return nullptr;
+}
+
+LBPLType Interpreter::visitTernaryExpr(TernaryExpr *expr) {
+  if (isTruthy(expr->condition->accept(this))) {
+    return expr->trueBranch->accept(this);
+  }
+
+  return expr->falseBranch->accept(this);
+}
+
+LBPLType Interpreter::visitVarExpr(VariableExpr *expr) {
+  return lookupVariable(expr->variable, expr);
+}
+LBPLType Interpreter::visitAssignExpr(AssignExpr *expr) {
+  LBPLType value = expr->value->accept(this);
+
+  auto it = locals.find(expr);
+  if (it == locals.end()) {
+    global->assign(expr->variable, value);
+  } else {
+    currentEnv->assignAt(it->second, expr->variable, value);
+  }
+
+  return value;
+}
+
+void Interpreter::execute(std::unique_ptr<Stmt> &stmt) { stmt->accept(this); }
+
+LBPLType Interpreter::evaluate(std::unique_ptr<Expr> &expr) {
+  return expr->accept(this);
+}
+
+LBPLType Interpreter::performBinaryOperation(std::shared_ptr<const Token> &op,
+                                             const LBPLType &left,
+                                             const LBPLType &right) {
+  auto performIntOp = [](int l, int r,
+                         std::shared_ptr<const Token> &op) -> LBPLType {
+    switch (op->type) {
+    case TokenType::Plus:
+      return l + r;
+    case TokenType::Minus:
+      return l - r;
+    case TokenType::Star:
+      return l * r;
+    case TokenType::Slash:
+      if (r == 0) {
+        throw RuntimeError(op.get(), "Division by zero.");
+      }
+      return l / r;
+    case TokenType::Less:
+      return l < r;
+    case TokenType::LessEqual:
+      return l <= r;
+    case TokenType::Greater:
+      return l > r;
+    case TokenType::GreaterEqual:
+      return l >= r;
+    case TokenType::EqualEqual:
+      return l == r;
+    case TokenType::BangEqual:
+      return l != r;
+    case TokenType::ModOp:
+      if (r == 0) {
+        throw RuntimeError(op.get(), "Modulo by zero.");
+      }
+      return l % r;
+    default:
+      throw RuntimeError(op.get(), "Unsupported binary operation.");
+    }
+  };
+
+  auto performDoubleOp = [](double l, double r,
+                            std::shared_ptr<const Token> &op) -> LBPLType {
+    switch (op->type) {
+    case TokenType::Plus:
+      return l + r;
+    case TokenType::Minus:
+      return l - r;
+    case TokenType::Star:
+      return l * r;
+    case TokenType::Slash:
+      if (r == 0) {
+        throw RuntimeError(op.get(), "Division by zero.");
+      }
+      return l / r;
+    case TokenType::Less:
+      return l < r;
+    case TokenType::LessEqual:
+      return l <= r;
+    case TokenType::Greater:
+      return l > r;
+    case TokenType::GreaterEqual:
+      return l >= r;
+    case TokenType::EqualEqual:
+      return l == r;
+    case TokenType::BangEqual:
+      return l != r;
+    default:
+      throw RuntimeError(op.get(), "Unsupported binary operation.");
+    }
+  };
+
+  auto performStringOp = [](const std::string &l, const std::string &r,
+                            std::shared_ptr<const Token> &op) -> LBPLType {
+    if (op->type == TokenType::Plus) {
+      return l + r;
+    } else {
+      throw RuntimeError(op.get(), "Unsupported binary operation.");
+    }
+  };
+
+  return std::visit(
+      [&](const auto &l, const auto &r) -> LBPLType {
+        using L = std::decay_t<decltype(l)>;
+        using R = std::decay_t<decltype(r)>;
+
+        if constexpr (std::is_same_v<L, int> && std::is_same_v<R, int>) {
+          return performIntOp(l, r, op);
+        } else if constexpr (std::is_same_v<L, double> &&
+                             std::is_same_v<R, double>) {
+          return performDoubleOp(l, r, op);
+        } else if constexpr ((std::is_same_v<L, std::string> &&
+                              std::is_same_v<R, int>) ||
+                             (std::is_same_v<L, int> &&
+                              std::is_same_v<R, std::string>)) {
+          // Handle int-to-string or string-to-int concatenation
+          std::string leftStr = std::holds_alternative<int>(left)
+                                    ? std::to_string(std::get<int>(left))
+                                    : std::get<std::string>(left);
+          std::string rightStr = std::holds_alternative<int>(right)
+                                     ? std::to_string(std::get<int>(right))
+                                     : std::get<std::string>(right);
+          if (op->type == TokenType::Plus) {
+            return leftStr + rightStr;
+          } else {
+            throw RuntimeError(op.get(), "Unsupported binary operation.");
+          }
+        } else if constexpr ((std::is_same_v<L, std::string> &&
+                              std::is_same_v<R, double>) ||
+                             (std::is_same_v<L, double> &&
+                              std::is_same_v<R, std::string>)) {
+          // Handle double-to-string or string-to-double concatenation
+          std::string leftStr = std::holds_alternative<double>(left)
+                                    ? std::to_string(std::get<double>(left))
+                                    : std::get<std::string>(left);
+          std::string rightStr = std::holds_alternative<double>(right)
+                                     ? std::to_string(std::get<double>(right))
+                                     : std::get<std::string>(right);
+          if (op->type == TokenType::Plus) {
+            return leftStr + rightStr;
+          } else {
+            throw RuntimeError(op.get(), "Unsupported binary operation.");
+          }
+        } else if constexpr (std::is_same_v<L, std::string> &&
+                             std::is_same_v<R, std::string>) {
+          return performStringOp(l, r, op);
+        } else {
+          throw RuntimeError(op.get(), "Unsupported binary operation.");
+        }
+      },
+      left, right);
+}
+
+/* template <typename T, typename G> */
+/* LBPLType Interpreter::performBinaryOperation(const Token *op, const T &left,
+ */
+/*                                              const G &right) { */
+/*   switch (op->type) { */
+/*   case TokenType::EqualEqual: */
+/*     return left == right; */
+/*   case TokenType::BangEqual: */
+/*     return left != right; */
+/*   case TokenType::Less: */
+/*     return left < right; */
+/*   case TokenType::LessEqual: */
+/*     return left <= right; */
+/*   case TokenType::Greater: */
+/*     return left > right; */
+/*   case TokenType::GreaterEqual: */
+/*     return left >= right; */
+/*   case TokenType::Plus: */
+/*     return left + right; */
+/*   case TokenType::Minus: */
+/*     return left - right; */
+/*   case TokenType::Star: */
+/*     return left * right; */
+/*   case TokenType::Slash: */
+/*     if (right == T(0)) { */
+/*       throw RuntimeError(op, "Division by zero."); */
+/*     } */
+/*     return left / right; */
+/*   case TokenType::ModOp: */
+/*     return (int)left % (int)right; */
+/*   default: */
+/*     throw RuntimeError(op, "Unsupported binary operation."); */
+/*   } */
+/* } */
+
+void Interpreter::executeBlock(std::vector<std::unique_ptr<Stmt>> &body,
+                               std::shared_ptr<Environment> &&env) {
+  executeBlock(body, env);
+}
+
+void Interpreter::executeBlock(std::vector<std::unique_ptr<Stmt>> &body,
+                               std::shared_ptr<Environment> &env) {
+  auto prev = currentEnv;
+
+  try {
+    currentEnv = env;
+    for (auto &&stmt : body) {
+      stmt->accept(this);
+    }
+  } catch (ReturnException &e) {
+    currentEnv = prev;
+    throw e;
+  }
+
+  currentEnv = prev;
+}
+
+bool Interpreter::isTruthy(const LBPLType &value) {
+  if (std::holds_alternative<std::nullptr_t>(value)) {
+    return std::get<std::nullptr_t>(value) != nullptr;
+  } else if (std::holds_alternative<bool>(value)) {
+    return std::get<bool>(value);
+  } else if (std::holds_alternative<int>(value)) {
+    return std::get<int>(value) == 0;
+  } else if (std::holds_alternative<double>(value)) {
+    return std::get<double>(value) == 0;
+  }
+
+  return false;
+}
+
+bool Interpreter::isTruthy(LBPLType &&value) { return isTruthy(value); }
+
+LBPLType Interpreter::lookupVariable(std::shared_ptr<const Token> &name,
+                                     Expr *expr) {
+  auto it = locals.find(expr);
+
+  if (it == locals.end()) {
+    return global->get(name);
+  }
+
+  return currentEnv->getAt(it->second, name);
+}
diff --git a/src/lexer.cpp b/src/lexer.cpp
new file mode 100755 (executable)
index 0000000..098bd9a
--- /dev/null
@@ -0,0 +1,337 @@
+#include "lexer.h"
+
+Lexer::Lexer(std::ifstream &stream, const std::string &filename)
+    : stream(stream), line(1), hadError(false), filename(filename) {
+  goToNextLine();
+}
+
+bool Lexer::isAtEnd() const { return stream.eof(); }
+bool Lexer::isAtLineEnd() const { return *current == '\0'; }
+bool Lexer::isDigit(char ch) const { return ch >= '0' && ch <= '9'; }
+bool Lexer::isAlpha(char ch) const {
+  return ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch == '_';
+}
+bool Lexer::match(char ch) {
+  if (isAtLineEnd() || ch != *current) {
+    return false;
+  }
+
+  advance();
+  return true;
+}
+
+void Lexer::goToNextLine() {
+  std::getline(stream, currentLine);
+  current = currentLine.begin();
+  start = currentLine.begin();
+}
+
+char Lexer::peek() const { return *current; }
+char Lexer::peekNext() const { return *(current + 1); }
+char Lexer::advance() {
+  if (isAtLineEnd()) {
+    if (isAtEnd()) {
+      return '\0';
+    }
+
+    std::getline(stream, currentLine);
+    current = currentLine.begin();
+
+    return *current;
+  }
+
+  return *(current++);
+}
+
+TokenType Lexer::checkKeyword(int startIndex, const std::string &restOfKeyword,
+                              TokenType typeIfMatch) {
+  if (current - (start + startIndex) != restOfKeyword.length()) {
+    return TokenType::Identifier;
+  }
+
+  for (int i = 0; i < restOfKeyword.length(); i++) {
+    if (*(start + startIndex + i) != restOfKeyword[i]) {
+      return TokenType::Identifier;
+    }
+  }
+
+  return typeIfMatch;
+}
+
+TokenType Lexer::isIdentifierOrKeywork() {
+  switch (*start) {
+  case 'b':
+    return checkKeyword(1, "reak", TokenType::Break);
+  case 'c':
+    if (current - start > 1) {
+      switch (*(start + 1)) {
+      case 'l':
+        return checkKeyword(2, "ass", TokenType::Class);
+      case 'o':
+        return checkKeyword(2, "ntinue", TokenType::Continue);
+      }
+    }
+
+    break;
+  case 'e':
+    return checkKeyword(1, "lse", TokenType::Else);
+  case 'f':
+    if (current - start > 1) {
+      switch (*(start + 1)) {
+      case 'a':
+        return checkKeyword(2, "lse", TokenType::False);
+      case 'o':
+        return checkKeyword(2, "r", TokenType::For);
+      case 'n':
+        return checkKeyword(2, "", TokenType::Fn);
+      }
+    }
+
+    break;
+  case 'i':
+    if (current - start > 1) {
+      switch (*(start + 1)) {
+      case 'f':
+        return checkKeyword(2, "", TokenType::If);
+      case 'm':
+        return checkKeyword(2, "port", TokenType::Import);
+      }
+    }
+
+    break;
+  case 'l':
+    if (current - start > 1) {
+      switch (*(start + 1)) {
+      case 'e':
+        return checkKeyword(2, "t", TokenType::Let);
+      case 'o':
+        return checkKeyword(2, "op", TokenType::Loop);
+      }
+    }
+    break;
+  case 'n':
+    return checkKeyword(1, "il", TokenType::Nil);
+  case 'r':
+    return checkKeyword(1, "eturn", TokenType::Return);
+  case 's':
+    return checkKeyword(1, "uper", TokenType::Super);
+  case 't':
+    if (current - start > 1) {
+      switch (*(start + 1)) {
+      case 'r':
+        return checkKeyword(2, "ue", TokenType::True);
+      case 'h':
+        return checkKeyword(2, "is", TokenType::This);
+      }
+    }
+    break;
+  case 'w':
+    return checkKeyword(1, "hile", TokenType::While);
+  }
+
+  return TokenType::Identifier;
+}
+
+std::shared_ptr<const Token> Lexer::makeToken(TokenType type,
+                                              std::string value) {
+  if (value != "") {
+    return std::make_shared<const Token>(type, value, line,
+                                         start - currentLine.begin(), filename);
+  }
+
+  return std::make_shared<const Token>(type, std::string(start, current), line,
+                                       start - currentLine.begin(), filename);
+}
+
+std::shared_ptr<const Token> Lexer::makeNumberToken() {
+  while (isDigit(peek())) {
+    advance();
+  }
+
+  if (peek() == '.' && isDigit(peekNext())) {
+    advance();
+    while (isDigit(peek())) {
+      advance();
+    }
+  }
+
+  return makeToken(TokenType::Number);
+}
+
+std::shared_ptr<const Token> Lexer::makeIdentifierToken() {
+  while (isAlpha(peek()) || isDigit(peek()) || peek() < 0) {
+    advance();
+  }
+
+  return makeToken(isIdentifierOrKeywork());
+}
+
+std::shared_ptr<const Token> Lexer::makeErrorToken(std::string msg) {
+  hadError = true;
+  return std::make_shared<const Token>(TokenType::Error, msg, line,
+                                       start - currentLine.begin(), filename);
+}
+
+void Lexer::skipWhitespace() {
+  do {
+    switch (peek()) {
+    case '\0':
+      line++;
+    case ' ':
+    case '\r':
+    case '\t':
+      advance();
+      break;
+    case '#':
+      while (!isAtLineEnd()) {
+        advance();
+      }
+
+      break;
+    default:
+      return;
+    }
+  } while (!isAtEnd());
+}
+
+std::shared_ptr<const Token> Lexer::getNextToken() {
+  skipWhitespace();
+  start = current;
+
+  char ch = advance();
+
+  if (isDigit(ch)) {
+    return makeNumberToken();
+  } else if (isAlpha(ch) || (int)ch < 0) {
+    return makeIdentifierToken();
+  }
+
+  switch (ch) {
+  case '(':
+    return makeToken(TokenType::LeftParen);
+  case ')':
+    return makeToken(TokenType::RightParen);
+  case '{':
+    return makeToken(TokenType::LeftBrace);
+  case '}':
+    return makeToken(TokenType::RightBrace);
+  case '?':
+    return makeToken(TokenType::Question);
+  case ',':
+    return makeToken(TokenType::Comma);
+  case '.':
+    return makeToken(TokenType::Dot);
+  case ':':
+    return makeToken(TokenType::Colon);
+  case ';':
+    return makeToken(TokenType::Semicolon);
+  case '%':
+    return makeToken(TokenType::ModOp);
+  case '&':
+    if (match('&')) {
+      return makeToken(TokenType::And);
+    }
+    return makeErrorToken("Invalid token '" + std::to_string(*current) + "'.");
+  case '|':
+    if (match('|')) {
+      return makeToken(TokenType::Or);
+    }
+    return makeErrorToken("Invalid operator '|' in: `" + currentLine +
+                          "`.\n\tDid you mean '|\033[4;32m|\033[0m'?");
+  case '-':
+    return makeToken(TokenType::Minus);
+  case '+':
+    return makeToken(TokenType::Plus);
+  case '/':
+    return makeToken(TokenType::Slash);
+  case '*':
+    return makeToken(TokenType::Star);
+  case '!':
+    if (match('=')) {
+      return makeToken(TokenType::BangEqual);
+    }
+    return makeToken(TokenType::Bang);
+  case '=':
+    if (match('=')) {
+      return makeToken(TokenType::EqualEqual);
+    }
+    return makeToken(TokenType::Equal);
+  case '>':
+    if (match('>')) {
+      return makeToken(TokenType::ShiftRight);
+    }
+    if (match('=')) {
+      return makeToken(TokenType::GreaterEqual);
+    }
+    return makeToken(TokenType::Greater);
+  case '<':
+    if (match('<')) {
+      return makeToken(TokenType::ShiftLeft);
+    }
+    if (match('=')) {
+      return makeToken(TokenType::LessEqual);
+    }
+    return makeToken(TokenType::Less);
+  case '\'': {
+    std::string val(1, advance());
+    if (!match('\'')) {
+      auto res =
+          makeErrorToken("A char can't be more then one character long.");
+      goToNextLine();
+      return res;
+    }
+    return makeToken(TokenType::Char, val);
+  }
+  case '"': {
+    std::string lexeme;
+
+    while (peek() != '"' && !isAtLineEnd()) {
+      if (peek() == '\\') {
+        advance();
+
+        switch (peek()) {
+        case 'n':
+          lexeme += "\n";
+          break;
+        case 't':
+          lexeme += "\t";
+          break;
+        case 'r':
+          lexeme += "\r";
+          break;
+        case '\'':
+          lexeme += "'";
+          break;
+        case '\"':
+          lexeme += "\"";
+          break;
+        case '\\':
+          lexeme += "\\";
+          break;
+        }
+
+        advance();
+      } else {
+        lexeme += advance();
+      }
+    }
+
+    if (isAtLineEnd()) {
+      return makeErrorToken("Unterminated string.");
+    }
+
+    advance();
+    return makeToken(TokenType::String, lexeme);
+  }
+  }
+
+  if (isAtEnd()) {
+    return makeToken(TokenType::Eof, "EoF");
+  }
+
+  return makeErrorToken("\033[1;36mHow did you get here?\033[0m");
+}
+
+int Lexer::getLine() { return line; }
+int Lexer::getColumn() { return current - currentLine.begin(); }
+std::string Lexer::getFilename() { return filename; }
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100755 (executable)
index 0000000..e2de6ed
--- /dev/null
@@ -0,0 +1,39 @@
+#include "main.h"
+
+int main(const int argc, const char **argv) {
+  if (argc < 2) {
+    std::cerr << "\033[1;31mNot enough arguemnts.\tUsage: lbpl [script]" << std::endl;
+    return 1;
+  }
+
+  std::ifstream sourceFile(argv[1]);
+  std::string filename(argv[1]);
+  if (!sourceFile.is_open()) {
+    std::cerr << "Error opening the file." << std::endl;
+    return 1;
+  }
+
+  Parser *parser = new Parser(sourceFile, filename);
+  std::vector<std::unique_ptr<Stmt>> statements = parser->parse();
+
+  // AST_Printer test;
+  // for (auto &&stmt : statements) {
+  //   stmt->accept(&test);
+  // }
+
+  if (!parser->hadError) {
+    Interpreter *interpreter = new Interpreter();
+    Resolver *resolver = new Resolver(interpreter);
+    resolver->resolve(statements);
+
+    if (!resolver->hadError) {
+      interpreter->interpret(statements);
+    }
+
+    delete resolver;
+    delete interpreter;
+  }
+
+  delete parser;
+  sourceFile.close();
+}
diff --git a/src/parser.cpp b/src/parser.cpp
new file mode 100755 (executable)
index 0000000..7edf294
--- /dev/null
@@ -0,0 +1,577 @@
+#include "parser.h"
+
+// static size_t totalMem = 0;
+// void *operator new(size_t size) {
+//   totalMem += size;
+//   void *ptr = malloc(size + sizeof(size_t));
+//   if (ptr) {
+//     *((size_t *)ptr) = size;
+//     return (char *)ptr + sizeof(size_t);
+//   } else {
+//     throw std::bad_alloc();
+//   }
+//   // return malloc(size);
+// }
+
+// void operator delete(void *ptr) noexcept {
+//   if (ptr) {
+//     size_t *originalPtr = (size_t *)((char *)ptr - sizeof(size_t));
+//     size_t size = *originalPtr;
+//     totalMem -= size;
+//     free(originalPtr);
+//   }
+// }
+
+std::vector<std::unique_ptr<Stmt>> Parser::parse() {
+  std::vector<std::unique_ptr<Stmt>> stmts;
+
+  // {
+  //   Timer timer;
+  while (!isAtEnd()) {
+    try {
+      if (match(TokenType::Import)) {
+        for (auto &&stmt : importStmt()) {
+          stmts.emplace_back(std::move(stmt));
+        }
+      } else {
+        stmts.emplace_back(declaration());
+      }
+    } catch (SyntaxError &e) {
+      hadError = true;
+      std::cout << e.what();
+      synchronize();
+    }
+  }
+  // }
+
+  // std::cout << totalMem << "B\n";
+  return stmts;
+}
+
+std::unique_ptr<Stmt> Parser::declaration() {
+  if (match(TokenType::Let)) {
+    return varDecl();
+  } else if (match(TokenType::Fn)) {
+    return functionDecl("function");
+  } else if (match(TokenType::Class)) {
+    return classDecl();
+  }
+
+  return statement();
+}
+
+std::vector<std::unique_ptr<Stmt>> Parser::importStmt() {
+  std::shared_ptr<const Token> path =
+      consume("Expected path to file to import but instead got: '" +
+                  current->lexeme + "'.",
+              TokenType::String);
+
+  if (importedFiles.contains(path->lexeme)) {
+    throw SyntaxError(previous.get(),
+                      "Recursive file import: '" + path->lexeme +
+                          "' has already been imported or is the main file.");
+  }
+  importedFiles.insert(path->lexeme);
+
+  std::ifstream sourceFile(path->lexeme);
+  if (!sourceFile.is_open()) {
+    throw SyntaxError(previous.get(), "Error opening file: '" + path->lexeme +
+                                          "'.\nIs the path correct?");
+  }
+
+  CONSUME_SEMICOLON("import");
+
+  return (new Parser(sourceFile, path->lexeme, importedFiles))->parse();
+}
+
+std::unique_ptr<VarStmt> Parser::varDecl() {
+  int line = lexer->getLine(), col = lexer->getColumn();
+
+  std::shared_ptr<const Token> name =
+      consume("Expected variable name after 'let' keyword but instead got '" +
+                  current->lexeme + "'.",
+              TokenType::Identifier);
+
+  std::unique_ptr<Expr> value;
+  if (match(TokenType::Equal)) {
+    value = expression();
+  }
+
+  consume("Expected ';' at the end of a statement but instead got: '" +
+              current->lexeme + "'.",
+          TokenType::Semicolon);
+  return std::make_unique<VarStmt>(line, col, lexer->getFilename(), name,
+                                   value);
+}
+
+std::unique_ptr<FnStmt> Parser::functionDecl(const std::string &kind) {
+  std::vector<std::shared_ptr<const Token>> args;
+  int line = lexer->getLine(), col = lexer->getColumn();
+  std::shared_ptr<const Token> name =
+      consume("Expected " + kind + " name.", TokenType::Identifier);
+
+  consume("Expected argument list after trait function name but instead got '" +
+              current->lexeme + "'.",
+          TokenType::LeftParen);
+
+  if (!check(TokenType::RightParen)) {
+    do {
+      std::shared_ptr<const Token> arg = consume(
+          "Expected name of trait function parameter but instead got '" +
+              current->lexeme + "'.",
+          TokenType::Identifier);
+      args.push_back(arg);
+    } while (match(TokenType::Comma));
+  }
+  consume("Expected ')' but instead got '" + current->lexeme + "'.",
+          TokenType::RightParen);
+  consume("Expected '{' after function signature but instead got '" +
+              current->lexeme + "'.",
+          TokenType::LeftBrace);
+
+  return std::make_unique<FnStmt>(line, col, lexer->getFilename(), name, args,
+                                  stmtSequence());
+}
+
+std::unique_ptr<ClassStmt> Parser::classDecl() {
+  int line = lexer->getLine(), col = lexer->getColumn();
+
+  std::shared_ptr<const Token> name =
+      consume("Expected class name but instead got '" + current->lexeme + "'.",
+              TokenType::Identifier);
+
+  std::unique_ptr<VariableExpr> superclass;
+  if (match(TokenType::Colon)) {
+    std::shared_ptr<const Token> supername =
+        consume("Expected superclass name.", TokenType::Identifier);
+    superclass = std::make_unique<VariableExpr>(line, col, lexer->getFilename(),
+                                                supername);
+  }
+
+  auto clas = std::make_unique<ClassStmt>(line, col, lexer->getFilename(), name,
+                                          superclass,
+                                          std::vector<std::unique_ptr<Stmt>>());
+  if (match(TokenType::Semicolon)) {
+    return clas;
+  } else if (match(TokenType::LeftBrace)) {
+    while (!check(TokenType::RightBrace) && !isAtEnd()) {
+      clas->body.emplace_back(functionDecl("method"));
+    }
+    consume("Expected closing '}'.", TokenType::RightBrace);
+
+    return clas;
+  } else {
+    throw SyntaxError(current.get(),
+                      "Expected either a semicolon for an inline class "
+                      "definition or a sequence of methods but instead got '" +
+                          current->lexeme + "'.");
+  }
+}
+
+std::unique_ptr<Stmt> Parser::statement() {
+  if (match(TokenType::LeftBrace)) {
+    return scopedStmt();
+  } else if (match(TokenType::If)) {
+    return ifStmt();
+  } else if (match(TokenType::While)) {
+    return whileStmt();
+  } else if (match(TokenType::Loop)) {
+    return loopStmt();
+  } else if (match(TokenType::For)) {
+    return forStmt();
+  } else if (match(TokenType::Return)) {
+    return returnStmt();
+  }
+
+  return expressionStmt();
+}
+
+std::unique_ptr<Stmt> Parser::scopedStmt() {
+  int line = lexer->getLine(), col = lexer->getColumn();
+  return std::make_unique<ScopedStmt>(line, col, lexer->getFilename(),
+                                      stmtSequence());
+}
+
+std::unique_ptr<Stmt> Parser::ifStmt() {
+  int line = lexer->getLine(), col = lexer->getColumn();
+  std::unique_ptr<Expr> cond = expression();
+  std::unique_ptr<Stmt> trueBranch = statement();
+  std::unique_ptr<Stmt> falseBranch;
+
+  if (match(TokenType::Else)) {
+    falseBranch = statement();
+  }
+
+  return std::make_unique<IfStmt>(line, col, lexer->getFilename(), cond,
+                                  trueBranch, falseBranch);
+}
+
+std::unique_ptr<Stmt> Parser::whileStmt() {
+  int line = lexer->getLine(), col = lexer->getColumn();
+
+  auto condition = expression();
+  auto body = statement();
+  return std::make_unique<WhileStmt>(line, col, lexer->getFilename(), condition,
+                                     body);
+}
+
+std::unique_ptr<Stmt> Parser::loopStmt() {
+  int line = lexer->getLine(), col = lexer->getColumn();
+
+  std::unique_ptr<Expr> cond = std::make_unique<LiteralExpr>(
+      line, col, lexer->getFilename(),
+      std::make_shared<const Token>(TokenType::True, "true", previous->line,
+                                    previous->column, previous->filename));
+  return std::make_unique<WhileStmt>(line, col, lexer->getFilename(), cond,
+                                     statement());
+}
+
+std::unique_ptr<Stmt> Parser::forStmt() {
+  int line = lexer->getLine(), col = lexer->getColumn();
+
+  consume("Expected '(' at the beginning of for statement but instead got '" +
+              current->lexeme + "'.",
+          TokenType::LeftParen);
+
+  std::unique_ptr<Stmt> initializer;
+  if (match(TokenType::Let)) {
+    initializer = varDecl();
+  } else if (!match(TokenType::Semicolon)) {
+    initializer = expressionStmt();
+  }
+
+  std::unique_ptr<Expr> cond;
+  if (!check(TokenType::Semicolon)) {
+    cond = expression();
+  }
+  consume("Expected ';' after loop condition but instead got '" +
+              current->lexeme + "'.",
+          TokenType::Semicolon);
+
+  std::unique_ptr<Expr> increment;
+  if (!check(TokenType::RightParen)) {
+    increment = expression();
+  }
+  consume("Expected ')' after loop clauses but instead got '" +
+              current->lexeme + "'.",
+          TokenType::RightParen);
+
+  std::unique_ptr<Stmt> body = statement();
+
+  if (!cond) {
+    cond = std::make_unique<LiteralExpr>(
+        line, col, lexer->getFilename(),
+        std::make_shared<const Token>(TokenType::True, "true", previous->line,
+                                      previous->column, previous->filename));
+  }
+
+  return std::make_unique<ForStmt>(line, col, lexer->getFilename(), initializer,
+                                   cond, increment, body);
+}
+
+std::unique_ptr<Stmt> Parser::returnStmt() {
+  int line = lexer->getLine(), col = lexer->getColumn();
+  std::unique_ptr<Expr> value;
+
+  if (!check(TokenType::Semicolon)) {
+    value = expression();
+  }
+  CONSUME_SEMICOLON("return");
+
+  return std::make_unique<ReturnStmt>(line, col, lexer->getFilename(), value);
+}
+
+std::unique_ptr<Stmt> Parser::expressionStmt() {
+  int line = lexer->getLine(), col = lexer->getColumn();
+
+  auto expr =
+      std::make_unique<ExprStmt>(line, col, lexer->getFilename(), expression());
+  CONSUME_SEMICOLON("expression");
+
+  return expr;
+}
+
+std::unique_ptr<Expr> Parser::expression() {
+  if (match(TokenType::Break)) {
+    int line = lexer->getLine(), col = lexer->getColumn();
+    return std::make_unique<BreakExpr>(line, col, lexer->getFilename());
+  } else if (match(TokenType::Break)) {
+    int line = lexer->getLine(), col = lexer->getColumn();
+    return std::make_unique<ContinueExpr>(line, col, lexer->getFilename());
+  }
+
+  return assignment();
+}
+
+std::unique_ptr<Expr> Parser::assignment() {
+  std::unique_ptr<Expr> left = orExpr();
+  int line = lexer->getLine(), col = lexer->getColumn();
+
+  if (match(TokenType::Equal)) {
+    std::unique_ptr<Expr> value = assignment();
+    if (auto var = dynamic_cast<VariableExpr *>(left.get())) {
+      return std::make_unique<AssignExpr>(line, col, lexer->getFilename(),
+                                          var->variable, value);
+    } else if (auto get = dynamic_cast<GetFieldExpr *>(left.get())) {
+      return std::make_unique<SetFieldExpr>(line, col, lexer->getFilename(),
+                                            get->instance, get->field, value);
+    }
+
+    throw SyntaxError(value.get(), "Invalid assignment value.");
+  } else if (match(TokenType::Question)) {
+    std::unique_ptr<Expr> trueExpr = assignment();
+    consume("Expected ':' between ternary expressions but instead got '" +
+                current->lexeme + "'.",
+            TokenType::Colon);
+
+    return std::make_unique<TernaryExpr>(line, col, lexer->getFilename(), left,
+                                         trueExpr, assignment());
+  }
+
+  return left;
+}
+
+std::unique_ptr<Expr> Parser::orExpr() {
+  std::unique_ptr<Expr> left = andExpr();
+  int line = lexer->getLine(), col = lexer->getColumn();
+
+  while (match(TokenType::Or)) {
+    std::shared_ptr<const Token> op = previous;
+
+    left = std::make_unique<BinaryExpr>(line, col, lexer->getFilename(), left,
+                                        andExpr(), op);
+  }
+
+  return left;
+}
+
+std::unique_ptr<Expr> Parser::andExpr() {
+  std::unique_ptr<Expr> left = equality();
+  int line = lexer->getLine(), col = lexer->getColumn();
+
+  while (match(TokenType::And)) {
+    std::shared_ptr<const Token> op = previous;
+
+    left = std::make_unique<BinaryExpr>(line, col, lexer->getFilename(), left,
+                                        equality(), op);
+  }
+
+  return left;
+}
+
+std::unique_ptr<Expr> Parser::equality() {
+  std::unique_ptr<Expr> left = comparison();
+  int line = lexer->getLine(), col = lexer->getColumn();
+
+  while (match(TokenType::EqualEqual, TokenType::BangEqual)) {
+    std::shared_ptr<const Token> op = previous;
+
+    left = std::make_unique<BinaryExpr>(line, col, lexer->getFilename(), left,
+                                        comparison(), op);
+  }
+
+  return left;
+}
+
+std::unique_ptr<Expr> Parser::comparison() {
+  std::unique_ptr<Expr> left = term();
+  int line = lexer->getLine(), col = lexer->getColumn();
+
+  while (match(TokenType::Greater, TokenType::GreaterEqual, TokenType::Less,
+               TokenType::LessEqual)) {
+    std::shared_ptr<const Token> op = previous;
+
+    left = std::make_unique<BinaryExpr>(line, col, lexer->getFilename(), left,
+                                        term(), op);
+  }
+
+  return left;
+}
+
+std::unique_ptr<Expr> Parser::term() {
+  std::unique_ptr<Expr> left = factor();
+  int line = lexer->getLine(), col = lexer->getColumn();
+
+  while (match(TokenType::Plus, TokenType::Minus, TokenType::ModOp)) {
+    std::shared_ptr<const Token> op = previous;
+
+    left = std::make_unique<BinaryExpr>(line, col, lexer->getFilename(), left,
+                                        factor(), op);
+  }
+
+  return left;
+}
+
+std::unique_ptr<Expr> Parser::factor() {
+  std::unique_ptr<Expr> left = unary();
+  int line = lexer->getLine(), col = lexer->getColumn();
+
+  while (match(TokenType::Star, TokenType::Slash)) {
+    std::shared_ptr<const Token> op = previous;
+
+    left = std::make_unique<BinaryExpr>(line, col, lexer->getFilename(), left,
+                                        unary(), op);
+  }
+
+  return left;
+}
+
+std::unique_ptr<Expr> Parser::unary() {
+  int line = lexer->getLine(), col = lexer->getColumn();
+  if (match(TokenType::Bang, TokenType::Minus)) {
+    std::shared_ptr<const Token> op = previous;
+    return std::make_unique<UnaryExpr>(line, col, lexer->getFilename(), unary(),
+                                       op);
+  }
+
+  return call();
+}
+
+std::unique_ptr<Expr> Parser::call() {
+  std::unique_ptr<Expr> expr = primary();
+  int line = lexer->getLine(), col = lexer->getColumn();
+
+  while (1) {
+    if (match(TokenType::LeftParen)) {
+      std::vector<std::unique_ptr<Expr>> args;
+      if (!check(TokenType::RightParen)) {
+        do {
+          args.emplace_back(expression());
+        } while (match(TokenType::Comma));
+      }
+
+      consume("Expected ')' after function call but instead got '" +
+                  current->lexeme + "'.",
+              TokenType::RightParen);
+      expr = std::make_unique<FnCallExpr>(line, col, lexer->getFilename(), expr,
+                                          args);
+    } else if (match(TokenType::Dot)) {
+      std::shared_ptr<const Token> prop =
+          consume("Expected class property or method but instead got '" +
+                      current->lexeme + "'.",
+                  TokenType::Identifier);
+      expr = std::make_unique<GetFieldExpr>(line, col, lexer->getFilename(),
+                                            expr, prop);
+    } else {
+      break;
+    }
+  }
+
+  return expr;
+}
+
+std::unique_ptr<Expr> Parser::primary() {
+  int line = lexer->getLine(), col = lexer->getColumn();
+
+  if (match(TokenType::Super)) {
+    consume("Expected '.' after super keyword.", TokenType::Dot);
+    std::shared_ptr<const Token> field =
+        consume("Expected superclass field.", TokenType::Identifier);
+
+    return std::make_unique<SuperExpr>(line, col, lexer->getFilename(), field);
+  } else if (match(TokenType::This)) {
+    return std::make_unique<ThisExpr>(line, col, lexer->getFilename());
+  } else if (match(TokenType::Identifier)) {
+    return std::make_unique<VariableExpr>(line, col, lexer->getFilename(),
+                                          previous);
+  } else if (match(TokenType::Number, TokenType::Char, TokenType::String,
+                   TokenType::True, TokenType::False, TokenType::Nil)) {
+    return std::make_unique<LiteralExpr>(line, col, lexer->getFilename(),
+                                         previous);
+  } else if (match(TokenType::LeftParen)) {
+    std::unique_ptr<Expr> expr = expression();
+    consume("Missing closing parenthesis ')' after expression.",
+            TokenType::RightParen);
+    return std::make_unique<GroupingExpr>(line, col, lexer->getFilename(),
+                                          expr);
+  }
+
+  throw SyntaxError(current.get(), "'" + current->lexeme +
+                                       "' not a valid expression statement.");
+}
+
+void Parser::synchronize() {
+  advance();
+
+  while (!isAtEnd()) {
+    if (previous->type == TokenType::Semicolon) {
+      return;
+    }
+
+    switch (current->type) {
+    case TokenType::Class:
+    case TokenType::Fn:
+    case TokenType::Let:
+    case TokenType::While:
+    case TokenType::Loop:
+    case TokenType::For:
+    case TokenType::If:
+    case TokenType::Return:
+      return;
+    }
+
+    advance();
+  }
+}
+
+bool Parser::isAtEnd() { return current->type == TokenType::Eof; }
+
+template <typename... TokenTypes>
+bool Parser::check(const TokenTypes &...types) {
+  return ((current->type == types) || ...);
+}
+
+template <typename... TokenTypes>
+bool Parser::match(const TokenTypes &...types) {
+  if (!(check(types) || ...)) {
+    return false;
+  }
+
+  advance();
+  return true;
+}
+
+std::shared_ptr<const Token> Parser::advance() {
+  previous = current;
+
+  if (isAtEnd()) {
+    return current;
+  }
+
+  current = lexer->getNextToken();
+  if (lexer->hadError) {
+    throw SyntaxError(current.get(), current->lexeme);
+  }
+  return previous;
+}
+
+template <typename... TokenTypes>
+std::shared_ptr<const Token> Parser::consume(const std::string &msg,
+                                             const TokenTypes &...types) {
+  if (!(check(types) || ...)) {
+    throw SyntaxError(current.get(), msg);
+  }
+
+  std::shared_ptr<const Token> res = current;
+  advance();
+  return res;
+}
+
+std::vector<std::unique_ptr<Stmt>> Parser::stmtSequence() {
+  std::vector<std::unique_ptr<Stmt>> stmts;
+
+  while (!check(TokenType::RightBrace) && !isAtEnd()) {
+    try {
+      stmts.push_back(declaration());
+    } catch (SyntaxError &e) {
+      hadError = true;
+      std::cout << e.what();
+      synchronize();
+    }
+  }
+
+  consume("Expected '}' at scope block end but instead got '" +
+              current->lexeme + "'.",
+          TokenType::RightBrace);
+  return stmts;
+}
diff --git a/src/resolver.cpp b/src/resolver.cpp
new file mode 100644 (file)
index 0000000..90bc24f
--- /dev/null
@@ -0,0 +1,258 @@
+#include "resolver.h"
+
+void Resolver::resolve(std::vector<std::unique_ptr<Stmt>> &stmts) {
+  for (auto &&stmt : stmts) {
+    try {
+      stmt->accept(this);
+    } catch (SyntaxError &e) {
+      std::cout << e.what();
+      hadError = true;
+    }
+  }
+}
+
+void Resolver::declare(const Token *name) {
+  if (scopes.empty()) {
+    return;
+  }
+
+  std::map<std::string, VarState> &scope = scopes.back();
+  if (scope.contains(name->lexeme)) {
+    throw SyntaxError(name, "Variable with this name already exists.");
+  }
+
+  scope.insert(std::make_pair(name->lexeme, VarState::Init));
+}
+
+void Resolver::define(const Token *name) {
+  if (scopes.empty()) {
+    return;
+  }
+
+  scopes.back().insert_or_assign(name->lexeme, VarState::Ready);
+}
+
+void Resolver::resolveLocal(Expr *expr, const Token *name) {
+  for (int i = scopes.size() - 1; i >= 0; i--) {
+    if (scopes[i].contains(name->lexeme)) {
+      interpreter->resolve(expr, scopes.size() - 1 - i);
+      return;
+    }
+  }
+}
+
+void Resolver::resolveLocal(Expr *expr, const std::string &name) {
+  for (int i = scopes.size() - 1; i >= 0; i--) {
+    if (scopes[i].contains(name)) {
+      interpreter->resolve(expr, scopes.size() - 1 - i);
+      return;
+    }
+  }
+}
+
+void Resolver::resolveFunction(FnStmt *fn, FunctionType::Type type) {
+  FunctionType::Type enclosingFn = currentFn;
+  currentFn = type;
+
+  beginScope();
+  for (auto &&arg : fn->args) {
+    declare(arg.get());
+    define(arg.get());
+  }
+
+  resolve(fn->body);
+  endScope();
+  currentFn = enclosingFn;
+}
+
+void Resolver::visitFnStmt(FnStmt *fn) {
+  declare(fn->name.get());
+  define(fn->name.get());
+  resolveFunction(fn, FunctionType::Function);
+}
+
+void Resolver::visitVarStmt(VarStmt *var) {
+  declare(var->name.get());
+  if (var->value) {
+    var->value->accept(this);
+  }
+
+  define(var->name.get());
+}
+
+void Resolver::visitClassStmt(ClassStmt *clas) {
+  declare(clas->name.get());
+  define(clas->name.get());
+
+  if (clas->superclass &&
+      clas->superclass->variable->lexeme == clas->name->lexeme) {
+    throw SyntaxError(clas, "A class can't inherit from itself.");
+  }
+
+  if (clas->superclass) {
+    currentClass = ClassType::Subclass;
+    clas->superclass->accept(this);
+
+    beginScope();
+    scopes.back().insert(std::make_pair("super", VarState::Ready));
+  } else {
+    currentClass = ClassType::None;
+  }
+
+  beginScope();
+  scopes.back().insert(std::make_pair("this", VarState::Ready));
+
+  for (auto &&stmt : clas->body) {
+    auto method = dynamic_cast<FnStmt *>(stmt.get());
+    if (method && method->name->lexeme == "init") {
+      resolveFunction(method, FunctionType::Initializer);
+    } else {
+      resolveFunction(method, FunctionType::Function);
+    }
+  }
+
+  endScope();
+
+  if (clas->superclass) {
+    endScope();
+  }
+}
+
+void Resolver::visitIfStmt(IfStmt *stmt) {
+  stmt->condition->accept(this);
+  stmt->trueBranch->accept(this);
+  if (stmt->falseBranch) {
+    stmt->falseBranch->accept(this);
+  }
+}
+
+void Resolver::visitWhileStmt(WhileStmt *loop) {
+  loop->condition->accept(this);
+  loops++;
+  loop->body->accept(this);
+  loops--;
+}
+
+void Resolver::visitForStmt(ForStmt *loop) {
+  beginScope();
+  loop->initializer->accept(this);
+  loop->condition->accept(this);
+  loop->increment->accept(this);
+  loops++;
+  loop->body->accept(this);
+  loops--;
+  endScope();
+}
+
+void Resolver::visitScopedStmt(ScopedStmt *block) {
+  beginScope();
+  resolve(block->body);
+  endScope();
+}
+
+void Resolver::visitExprStmt(ExprStmt *stmt) { stmt->expr->accept(this); }
+
+void Resolver::visitReturnStmt(ReturnStmt *ret) {
+  if (currentFn == FunctionType::None) {
+    throw SyntaxError(ret, "Can't return from top-level code");
+  } else if (ret->value) {
+    if (currentFn == FunctionType::Initializer) {
+      throw SyntaxError(ret, "Can't return a value from a class initializer.");
+    }
+
+    ret->value->accept(this);
+  }
+}
+
+LBPLType Resolver::visitBinaryExpr(BinaryExpr *expr) {
+  expr->left->accept(this);
+  expr->right->accept(this);
+  return nullptr;
+}
+
+LBPLType Resolver::visitBreakExpr(BreakExpr *expr) {
+  if (loops <= 0) {
+    throw SyntaxError(expr, "Can't break from outside of a loop.");
+  }
+  return nullptr;
+}
+
+LBPLType Resolver::visitContinueExpr(ContinueExpr *expr) {
+  if (loops <= 0) {
+    throw SyntaxError(expr, "Can't break from outside of a loop.");
+  }
+  return nullptr;
+}
+
+LBPLType Resolver::visitUnaryExpr(UnaryExpr *expr) {
+  expr->right->accept(this);
+  return nullptr;
+}
+
+LBPLType Resolver::visitLiteralExpr(LiteralExpr *) { return nullptr; }
+
+LBPLType Resolver::visitGroupExpr(GroupingExpr *expr) {
+  expr->expr->accept(this);
+  return nullptr;
+}
+
+LBPLType Resolver::visitSuperExpr(SuperExpr *expr) {
+  if (currentClass == ClassType::None) {
+    throw SyntaxError(expr, "Can't access 'super' from outside of class body.");
+  } else if (currentClass != ClassType::Subclass) {
+    throw SyntaxError(expr,
+                      "Can't access 'super' in a class without superclass.");
+  }
+
+  resolveLocal(expr, "super");
+  return nullptr;
+}
+
+LBPLType Resolver::visitThisExpr(ThisExpr *expr) {
+  resolveLocal(expr, "this");
+  return nullptr;
+}
+
+LBPLType Resolver::visitCallExpr(FnCallExpr *expr) {
+  expr->callee->accept(this);
+
+  for (auto &&arg : expr->args) {
+    arg->accept(this);
+  }
+  return nullptr;
+}
+
+LBPLType Resolver::visitGetFieldExpr(GetFieldExpr *expr) {
+  expr->instance->accept(this);
+  return nullptr;
+}
+
+LBPLType Resolver::visitSetFieldExpr(SetFieldExpr *expr) {
+  expr->value->accept(this);
+  expr->instance->accept(this);
+  return nullptr;
+}
+
+LBPLType Resolver::visitTernaryExpr(TernaryExpr *expr) {
+  expr->condition->accept(this);
+  expr->trueBranch->accept(this);
+  expr->falseBranch->accept(this);
+  return nullptr;
+}
+
+LBPLType Resolver::visitVarExpr(VariableExpr *expr) {
+  if (!scopes.empty() && scopes.back().contains(expr->variable->lexeme) &&
+      scopes.back().find(expr->variable->lexeme)->second != VarState::Ready) {
+    throw SyntaxError(expr, "You are trying to read the value of a variable "
+                            "that hasn't been defined yet.");
+  }
+
+  resolveLocal(expr, expr->variable.get());
+  return nullptr;
+}
+
+LBPLType Resolver::visitAssignExpr(AssignExpr *expr) {
+  expr->value->accept(this);
+  resolveLocal(expr, expr->variable.get());
+  return nullptr;
+}
diff --git a/src/runtime_error.cpp b/src/runtime_error.cpp
new file mode 100644 (file)
index 0000000..da079da
--- /dev/null
@@ -0,0 +1,22 @@
+#include "runtime_error.h"
+
+std::string RuntimeError::what() {
+  std::stringstream ss(msg);
+  std::string lineInfo = "\033[1;31m[line " + std::to_string(line) + ":" +
+                         std::to_string(column) + " in " + filename +
+                         "]\033[0m: ";
+  std::string line, res = lineInfo;
+  std::string indentation(lineInfo.length() - 11, ' ');
+  bool firstLine = true;
+
+  while (std::getline(ss, line)) {
+    if (firstLine) {
+      res += line + "\n";
+      firstLine = false;
+    } else {
+      res += indentation + line + "\n";
+    }
+  }
+
+  return res;
+}
diff --git a/src/syntax_error.cpp b/src/syntax_error.cpp
new file mode 100755 (executable)
index 0000000..fa7ef68
--- /dev/null
@@ -0,0 +1,22 @@
+#include "syntax_error.h"
+
+std::string SyntaxError::what() {
+  std::stringstream ss(msg);
+  std::string lineInfo = "\033[1;31m[line " + std::to_string(line) + ":" +
+                         std::to_string(column) + " in " + filename +
+                         "]\033[0m: ";
+  std::string line, res = lineInfo;
+  std::string indentation(lineInfo.length() - 11, ' ');
+  bool firstLine = true;
+
+  while (std::getline(ss, line)) {
+    if (firstLine) {
+      res += line + "\n";
+      firstLine = false;
+    } else {
+      res += indentation + line + "\n";
+    }
+  }
+
+  return res;
+}