From 9a1c9dc35087e80aa152c9e06ff058c2c58688ff Mon Sep 17 00:00:00 2001 From: LeonardoBizzoni Date: Sun, 3 Sep 2023 12:40:51 +0200 Subject: [PATCH] Finished interpreting class related statements --- .gitignore | 2 + CMakeLists.txt | 14 + README.org | 37 +++ lib/LBPLCallable.h | 15 ++ lib/LBPLClass.h | 29 +++ lib/LBPLFunction.h | 30 +++ lib/LBPLInstance.h | 24 ++ lib/LBPLTypes.h | 16 ++ lib/ast_printer.h | 36 +++ lib/builtin_methods.h | 48 ++++ lib/common.h | 10 + lib/environment.h | 42 +++ lib/expressions.h | 204 +++++++++++++++ lib/interpreter.h | 80 ++++++ lib/lexer.h | 56 ++++ lib/main.h | 18 ++ lib/parser.h | 82 ++++++ lib/resolver.h | 91 +++++++ lib/runtime_error.h | 33 +++ lib/statements.h | 133 ++++++++++ lib/syntax_error.h | 33 +++ lib/token.h | 20 ++ lib/token_type.h | 64 +++++ lib/tree_nodes.h | 31 +++ lib/visitor.h | 42 +++ src/LBPLClass.cpp | 30 +++ src/LBPLFunction.cpp | 30 +++ src/LBPLInstance.cpp | 19 ++ src/ast_printer.cpp | 215 ++++++++++++++++ src/environment.cpp | 89 +++++++ src/interpreter.cpp | 557 ++++++++++++++++++++++++++++++++++++++++ src/lexer.cpp | 337 ++++++++++++++++++++++++ src/main.cpp | 39 +++ src/parser.cpp | 577 ++++++++++++++++++++++++++++++++++++++++++ src/resolver.cpp | 258 +++++++++++++++++++ src/runtime_error.cpp | 22 ++ src/syntax_error.cpp | 22 ++ 37 files changed, 3385 insertions(+) create mode 100644 .gitignore create mode 100755 CMakeLists.txt create mode 100755 README.org create mode 100644 lib/LBPLCallable.h create mode 100644 lib/LBPLClass.h create mode 100644 lib/LBPLFunction.h create mode 100644 lib/LBPLInstance.h create mode 100644 lib/LBPLTypes.h create mode 100755 lib/ast_printer.h create mode 100644 lib/builtin_methods.h create mode 100755 lib/common.h create mode 100755 lib/environment.h create mode 100755 lib/expressions.h create mode 100644 lib/interpreter.h create mode 100755 lib/lexer.h create mode 100755 lib/main.h create mode 100755 lib/parser.h create mode 100644 lib/resolver.h create mode 100644 lib/runtime_error.h create mode 100755 lib/statements.h create mode 100755 lib/syntax_error.h create mode 100755 lib/token.h create mode 100755 lib/token_type.h create mode 100644 lib/tree_nodes.h create mode 100755 lib/visitor.h create mode 100644 src/LBPLClass.cpp create mode 100644 src/LBPLFunction.cpp create mode 100644 src/LBPLInstance.cpp create mode 100755 src/ast_printer.cpp create mode 100755 src/environment.cpp create mode 100644 src/interpreter.cpp create mode 100755 src/lexer.cpp create mode 100755 src/main.cpp create mode 100755 src/parser.cpp create mode 100644 src/resolver.cpp create mode 100644 src/runtime_error.cpp create mode 100755 src/syntax_error.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5b79a54 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +.cache/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..b93afa3 --- /dev/null +++ b/CMakeLists.txt @@ -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 index 0000000..e8d4a28 --- /dev/null +++ b/README.org @@ -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 index 0000000..b858e69 --- /dev/null +++ b/lib/LBPLCallable.h @@ -0,0 +1,15 @@ +#ifndef LBPL_CALLABLE_H +#define LBPL_CALLABLE_H + +#include "LBPLTypes.h" +#include + +class Interpreter; + +class LBPLCallable { +public: + virtual int arity() = 0; + virtual LBPLType call(Interpreter*, std::vector&) = 0; +}; + +#endif diff --git a/lib/LBPLClass.h b/lib/LBPLClass.h new file mode 100644 index 0000000..58ed0ac --- /dev/null +++ b/lib/LBPLClass.h @@ -0,0 +1,29 @@ +#ifndef LBPL_CLASS_H +#define LBPL_CLASS_H + +#include "LBPLCallable.h" +#include "LBPLFunction.h" + +#include +#include + +class LBPLClass : public LBPLCallable { +public: + std::string name; + std::shared_ptr superclass; + std::map methods; + +public: + LBPLClass(const std::string &name, std::shared_ptr &superclass, + std::map &methods) + : name(name), superclass(superclass), methods(methods) {} + LBPLClass(const std::string &name, std::map &methods) + : name(name), superclass(nullptr), methods(methods) {} + + LBPLFunc *findMethod(const std::string &); + + int arity() override; + LBPLType call(Interpreter *, std::vector &) override; +}; + +#endif diff --git a/lib/LBPLFunction.h b/lib/LBPLFunction.h new file mode 100644 index 0000000..de661e1 --- /dev/null +++ b/lib/LBPLFunction.h @@ -0,0 +1,30 @@ +#ifndef LBPL_FUNCTION_H +#define LBPL_FUNCTION_H + +#include "LBPLCallable.h" +#include "environment.h" +#include "statements.h" +#include + +class LBPLFunc : public LBPLCallable { +private: + FnStmt *stmt; + std::shared_ptr closureEnv; + bool isInitializer; + +public: + LBPLFunc(FnStmt *stmt, std::shared_ptr &closureEnv, + bool isInitializer) + : stmt(stmt), closureEnv(closureEnv), isInitializer(isInitializer) {} + LBPLFunc(FnStmt *stmt, std::shared_ptr &&closureEnv, + bool isInitializer) + : stmt(stmt), closureEnv(closureEnv), isInitializer(isInitializer) {} + + void bind(std::shared_ptr &&instance); + void bind(std::shared_ptr &instance); + + int arity() override; + LBPLType call(Interpreter *, std::vector &) override; +}; + +#endif diff --git a/lib/LBPLInstance.h b/lib/LBPLInstance.h new file mode 100644 index 0000000..a463142 --- /dev/null +++ b/lib/LBPLInstance.h @@ -0,0 +1,24 @@ +#ifndef LBPL_INSTANCE_H +#define LBPL_INSTANCE_H + +#include "LBPLClass.h" +#include "runtime_error.h" + +#include +#include + +class LBPLInstance { +private: + LBPLClass *lbplClass; + std::map 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 index 0000000..d37fa91 --- /dev/null +++ b/lib/LBPLTypes.h @@ -0,0 +1,16 @@ +#ifndef LBPL_TYPES_H +#define LBPL_TYPES_H + +#include +#include +#include + +class LBPLInstance; +class LBPLCallable; +class LBPLClass; + +using LBPLType = + std::variant, std::shared_ptr, + std::shared_ptr>; +#endif diff --git a/lib/ast_printer.h b/lib/ast_printer.h new file mode 100755 index 0000000..01be7d3 --- /dev/null +++ b/lib/ast_printer.h @@ -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 index 0000000..b04c01a --- /dev/null +++ b/lib/builtin_methods.h @@ -0,0 +1,48 @@ +#ifndef BUILTIN_METHODS_H +#define BUILTIN_METHODS_H + +#include "LBPLCallable.h" +#include "statements.h" + +#include +#include +#include + +class LBPLPrintln : public LBPLCallable { +public: + LBPLPrintln() {} + + int arity() override { return 1; }; + + LBPLType call(Interpreter *, std::vector &args) override { + if (std::holds_alternative(args[0])) { + std::cout << std::get(args[0]) << std::endl; + } else if (std::holds_alternative(args[0])) { + std::cout << std::get(args[0]) << std::endl; + } else if (std::holds_alternative(args[0])) { + std::cout << std::get(args[0]) << std::endl; + } else if (std::holds_alternative(args[0])) { + std::cout << std::get(args[0]) << std::endl; + } else if (std::holds_alternative(args[0])) { + std::cout << (std::get(args[0]) ? "true" : "false") << std::endl; + } + + return nullptr; + } +}; + +class LBPLClock : public LBPLCallable { +public: + LBPLClock() {} + + int arity() override { return 0; }; + + LBPLType call(Interpreter *, std::vector &args) override { + return std::chrono::duration_cast( + 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 index 0000000..fbdc444 --- /dev/null +++ b/lib/common.h @@ -0,0 +1,10 @@ +#ifndef COMMON_H +#define COMMON_H + +#include "token.h" +#include "token_type.h" + +#include +#include + +#endif diff --git a/lib/environment.h b/lib/environment.h new file mode 100755 index 0000000..6fa8cce --- /dev/null +++ b/lib/environment.h @@ -0,0 +1,42 @@ +#ifndef ENVIRONMENT_H +#define ENVIRONMENT_H + +#include "LBPLTypes.h" +#include "token.h" +#include "tree_nodes.h" + +#include +#include +#include +#include + +class Environment { +public: + std::map env; + std::shared_ptr enclosing; + +public: + Environment() : enclosing(nullptr) {} + Environment(Environment &other) + : env(other.env), + enclosing(other.enclosing ? other.enclosing->clone() : nullptr) {} + Environment(std::shared_ptr &enclosing) : enclosing(enclosing) {} + + std::shared_ptr clone() { + return std::make_shared(*this); + } + + void define(const std::string &, LBPLType &); + void define(const std::string &, LBPLType &&); + + void printEnv(const std::string &&); + + LBPLType get(std::shared_ptr &); + LBPLType getAt(int, std::shared_ptr &); + LBPLType getAt(int, const std::string &); + void assign(std::shared_ptr &, LBPLType &); + void assign(std::shared_ptr &, LBPLType &&); + void assignAt(int, std::shared_ptr &, LBPLType &); +}; + +#endif diff --git a/lib/expressions.h b/lib/expressions.h new file mode 100755 index 0000000..3b7f324 --- /dev/null +++ b/lib/expressions.h @@ -0,0 +1,204 @@ +#ifndef EXPRESSIONS_H +#define EXPRESSIONS_H + +#include "common.h" +#include "visitor.h" + +#include +#include +#include + +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 left; + std::unique_ptr right; + std::shared_ptr op; + + BinaryExpr(int line, int column, const std::string &file, + std::unique_ptr &left, std::unique_ptr &right, + std::shared_ptr &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 &left, std::unique_ptr &&right, + std::shared_ptr &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 right; + std::shared_ptr op; + + UnaryExpr(int line, int column, const std::string &file, + std::unique_ptr right, std::shared_ptr &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 token; + + LiteralExpr(int line, int column, const std::string &file, + std::shared_ptr &literal) + : token(literal), Expr(line, column, file) {} + LiteralExpr(int line, int column, const std::string &file, + std::shared_ptr &&literal) + : token(literal), Expr(line, column, file) {} + LBPLType accept(Expression::Visitor *visitor) { + return visitor->visitLiteralExpr(this); + } +}; + +struct SuperExpr : public Expr { + std::shared_ptr field; + + SuperExpr(int line, int column, const std::string &file, + std::shared_ptr &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; + + GroupingExpr(int line, int column, const std::string &file, + std::unique_ptr &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 variable; + + VariableExpr(int line, int column, const std::string &file, + std::shared_ptr &variable) + : variable(variable), Expr(line, column, file) {} + LBPLType accept(Expression::Visitor *visitor) { + return visitor->visitVarExpr(this); + } +}; + +struct AssignExpr : public Expr { + std::shared_ptr variable; + std::unique_ptr value; + + AssignExpr(int line, int column, const std::string &file, + std::shared_ptr &variable, + std::unique_ptr &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 callee; + std::vector> args; + + FnCallExpr(int line, int column, const std::string &file, + std::unique_ptr &callee, + std::vector> &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 condition; + std::unique_ptr trueBranch; + std::unique_ptr falseBranch; + + TernaryExpr(int line, int column, const std::string &file, + std::unique_ptr &condition, + std::unique_ptr &trueBranch, + std::unique_ptr &&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 field; + std::unique_ptr instance; + + GetFieldExpr(int line, int column, const std::string &file, + std::unique_ptr &instance, + std::shared_ptr &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 field; + std::unique_ptr value; + std::unique_ptr instance; + + SetFieldExpr(int line, int column, const std::string &file, + std::unique_ptr &instance, + std::shared_ptr &field, + std::unique_ptr &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 index 0000000..13650aa --- /dev/null +++ b/lib/interpreter.h @@ -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 +#include +#include + +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 global, currentEnv; + std::map locals; + +private: + void execute(std::unique_ptr &); + + LBPLType evaluate(std::unique_ptr &); + LBPLType lookupVariable(std::shared_ptr &, Expr *); + bool isTruthy(const LBPLType &); + bool isTruthy(LBPLType &&); + + LBPLType performBinaryOperation(std::shared_ptr &, + 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::shared_ptr &); + void executeBlock(std::vector> &, + std::shared_ptr &&); + void interpret(std::vector> &); + void resolve(Expr *, int); + + Interpreter() + : global(std::make_shared()), currentEnv(global), locals() { + global->define("println", std::make_shared()); + global->define("clock", std::make_shared()); + } +}; + +#endif diff --git a/lib/lexer.h b/lib/lexer.h new file mode 100755 index 0000000..7a3d68f --- /dev/null +++ b/lib/lexer.h @@ -0,0 +1,56 @@ +#ifndef LEXER_H +#define LEXER_H + +#include "common.h" + +#include +#include +#include +#include + +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 makeToken(TokenType, std::string = ""); + std::shared_ptr makeNumberToken(); + std::shared_ptr makeIdentifierToken(); + std::shared_ptr 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 getNextToken(); +}; + +#endif diff --git a/lib/main.h b/lib/main.h new file mode 100755 index 0000000..78d6836 --- /dev/null +++ b/lib/main.h @@ -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 +#include +#include + +std::string readFile(const char *); + +#endif diff --git a/lib/parser.h b/lib/parser.h new file mode 100755 index 0000000..daa8b76 --- /dev/null +++ b/lib/parser.h @@ -0,0 +1,82 @@ +#ifndef PARSER_H +#define PARSER_H + +#include "expressions.h" +#include "lexer.h" +#include "statements.h" +#include "syntax_error.h" + +#include +#include +#include +#include + +#define CONSUME_SEMICOLON(keyword) \ + consume("Expected ';' after " + std::string(keyword) + " statement.", \ + TokenType::Semicolon); + +class Parser { +private: + Lexer *lexer; + std::shared_ptr current; + std::shared_ptr previous; + std::unordered_set importedFiles; + +public: + bool hadError; + +private: + bool isAtEnd(); + void synchronize(); + std::shared_ptr advance(); + + template bool check(const TokenTypes &...); + template + std::shared_ptr consume(const std::string &msg, const TokenTypes &...); + template bool match(const TokenTypes &...); + + std::vector> importStmt(); + std::unique_ptr functionDecl(const std::string &); + std::unique_ptr varDecl(); + std::unique_ptr classDecl(); + + std::vector> stmtSequence(); + + std::unique_ptr declaration(); + std::unique_ptr statement(); + std::unique_ptr ifStmt(); + std::unique_ptr whileStmt(); + std::unique_ptr loopStmt(); + std::unique_ptr forStmt(); + std::unique_ptr returnStmt(); + std::unique_ptr scopedStmt(); + std::unique_ptr expressionStmt(); + + std::unique_ptr expression(); + std::unique_ptr assignment(); + std::unique_ptr orExpr(); + std::unique_ptr andExpr(); + std::unique_ptr equality(); + std::unique_ptr comparison(); + std::unique_ptr term(); + std::unique_ptr factor(); + std::unique_ptr unary(); + std::unique_ptr call(); + std::unique_ptr 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 &importedFiles) + : importedFiles(importedFiles), lexer(new Lexer(file, filename)), + current(lexer->getNextToken()), previous(current) {} + + std::vector> parse(); +}; + +#endif diff --git a/lib/resolver.h b/lib/resolver.h new file mode 100644 index 0000000..4783f6f --- /dev/null +++ b/lib/resolver.h @@ -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 +#include +#include +#include + +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> scopes; + +public: + bool hadError; + +private: + void beginScope() { scopes.emplace_back(std::map()); } + 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> &); +}; + +#endif diff --git a/lib/runtime_error.h b/lib/runtime_error.h new file mode 100644 index 0000000..1d791de --- /dev/null +++ b/lib/runtime_error.h @@ -0,0 +1,33 @@ +#ifndef RUNTIME_ERROR_H +#define RUNTIME_ERROR_H + +#include "token.h" +#include "expressions.h" +#include "statements.h" + +#include +#include +#include +#include + +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 index 0000000..992f489 --- /dev/null +++ b/lib/statements.h @@ -0,0 +1,133 @@ +#ifndef STATEMENTS_H +#define STATEMENTS_H + +#include "expressions.h" + +#include +#include +#include + +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 name; + std::vector > args; + std::vector> body; + + FnStmt(int line, int column, const std::string &file, std::shared_ptr &name, + std::vector> &args, std::vector> &&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 name; + std::unique_ptr value; + + VarStmt(int line, int column, const std::string &file, std::shared_ptr &name, + std::unique_ptr &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 name; + std::unique_ptr superclass; + std::vector> body; + + ClassStmt(int line, int column, const std::string &file, std::shared_ptr &name, + std::unique_ptr &superclass, + std::vector> &&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 condition; + std::unique_ptr trueBranch; + std::unique_ptr falseBranch; + + IfStmt(int line, int column, const std::string &file, + std::unique_ptr &condition, + std::unique_ptr &trueBranch, std::unique_ptr &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 condition; + std::unique_ptr body; + + WhileStmt(int line, int column, const std::string &file, + std::unique_ptr &cond, std::unique_ptr &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 &cond, std::unique_ptr &&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 increment, condition; + std::unique_ptr initializer, body; + + ForStmt(int line, int column, const std::string &file, + std::unique_ptr &initializer, std::unique_ptr &cond, + std::unique_ptr &increment, std::unique_ptr &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> body; + + ScopedStmt(int line, int column, const std::string &file, + std::vector> &&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; + + ExprStmt(int line, int column, const std::string &file, + std::unique_ptr &&expr) + : expr(std::move(expr)), Stmt(line, column, file) {} + + void accept(Statement::Visitor *visitor) { visitor->visitExprStmt(this); } +}; + +struct ReturnStmt : public Stmt { + std::unique_ptr value; + + ReturnStmt(int line, int column, const std::string &file, + std::unique_ptr &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 index 0000000..48c17c8 --- /dev/null +++ b/lib/syntax_error.h @@ -0,0 +1,33 @@ +#ifndef SYNTAX_ERROR_H +#define SYNTAX_ERROR_H + +#include "token.h" +#include "expressions.h" +#include "statements.h" + +#include +#include +#include +#include + +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 index 0000000..47c4be1 --- /dev/null +++ b/lib/token.h @@ -0,0 +1,20 @@ +#ifndef TOKEN_H +#define TOKEN_H + +#include "token_type.h" + +#include + +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 index 0000000..b30e872 --- /dev/null +++ b/lib/token_type.h @@ -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 index 0000000..214c372 --- /dev/null +++ b/lib/tree_nodes.h @@ -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 index 0000000..37f75d8 --- /dev/null +++ b/lib/visitor.h @@ -0,0 +1,42 @@ +#ifndef VISITOR_H +#define VISITOR_H + +#include "LBPLTypes.h" +#include "tree_nodes.h" + +#include + +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 index 0000000..ba83e5f --- /dev/null +++ b/src/LBPLClass.cpp @@ -0,0 +1,30 @@ +#include "LBPLClass.h" +#include "LBPLInstance.h" +#include "interpreter.h" + +LBPLType LBPLClass::call(Interpreter *interpreter, std::vector &args) { + auto instance = std::make_shared(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 index 0000000..4d95259 --- /dev/null +++ b/src/LBPLFunction.cpp @@ -0,0 +1,30 @@ +#include "LBPLFunction.h" +#include "interpreter.h" + +void LBPLFunc::bind(std::shared_ptr &&instance) { + closureEnv = std::make_shared(); + closureEnv->define("this", instance); +} + +void LBPLFunc::bind(std::shared_ptr &instance) { + closureEnv = std::make_shared(); + closureEnv->define("this", instance); +} + +int LBPLFunc::arity() { return stmt->args.size(); } + +LBPLType LBPLFunc::call(Interpreter *interpreter, std::vector &args) { + auto env = std::make_shared(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 index 0000000..66df9f8 --- /dev/null +++ b/src/LBPLInstance.cpp @@ -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(this)); + return std::make_shared(*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 index 0000000..2fbb2c0 --- /dev/null +++ b/src/ast_printer.cpp @@ -0,0 +1,215 @@ +#include "ast_printer.h" +#include "statements.h" + +#include + +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 index 0000000..ef9a29a --- /dev/null +++ b/src/environment.cpp @@ -0,0 +1,89 @@ +#include "environment.h" +#include "LBPLTypes.h" +#include "runtime_error.h" +#include +#include +#include +#include + +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 &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 &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 &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 &name, LBPLType &&value) { + assign(name, value); +} + +void Environment::assignAt(int depth, std::shared_ptr &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(value)) { + std::cout << "int" << std::endl; + } else if (std::holds_alternative(value)) { + std::cout << "double" << std::endl; + } else if (std::holds_alternative(value)) { + std::cout << "char" << std::endl; + } else if (std::holds_alternative(value)) { + std::cout << "nullptr" << std::endl; + } else if (std::holds_alternative>(value)) { + std::cout << "callable" << std::endl; + } else if (std::holds_alternative>(value)) { + std::cout << "class" << std::endl; + } else if (std::holds_alternative>(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 index 0000000..42432bd --- /dev/null +++ b/src/interpreter.cpp @@ -0,0 +1,557 @@ +#include "interpreter.h" +#include "LBPLClass.h" +#include "LBPLFunction.h" +#include "LBPLInstance.h" +#include "LBPLTypes.h" +#include "runtime_error.h" + +#include +#include +#include +#include +#include +#include +class Timer { + std::chrono::time_point 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( + m_startTimepoint) + .time_since_epoch() + .count(); + auto end = + std::chrono::time_point_cast(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> &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(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>(superclass)) { + throw RuntimeError(stmt->superclass.get(), + "Superclass must be another class."); + } + + currentEnv = std::make_shared(currentEnv); + currentEnv->define("super", + std::get>(superclass)); + } + + std::map methods; + for (auto &&methodStmt : stmt->body) { + auto method = dynamic_cast(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( + stmt->name->lexeme, + std::get>(superclass), + methods)); + } else { + currentEnv->assign( + stmt->name, std::make_shared(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(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(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(left) && + * std::holds_alternative(right)) { */ + /* return performBinaryOperation(expr->op.get(), std::get(left), */ + /* std::get(right)); */ + /* } else if (std::holds_alternative(left) && */ + /* std::holds_alternative(right)) { */ + /* return performBinaryOperation(expr->op.get(), std::get(left), */ + /* std::get(right)); */ + /* } else if (std::holds_alternative(left) && */ + /* std::holds_alternative(right)) { */ + /* return performBinaryOperation(expr->op.get(), std::get(left), */ + /* std::get(right)); */ + /* } else if (std::holds_alternative(left) && */ + /* std::holds_alternative(right)) { */ + /* return performBinaryOperation(expr->op.get(), std::get(left), */ + /* std::get(right)); */ + /* } else if ((std::holds_alternative(left) || */ + /* std::holds_alternative(right)) && */ + /* expr->op->type == TokenType::Plus) { */ + /* if (std::holds_alternative(left) && */ + /* std::holds_alternative(right)) { */ + /* return std::get(left) + */ + /* std::get(right); */ + /* } else if (std::holds_alternative(left) && */ + /* std::holds_alternative(right)) { */ + /* return std::get(left) + */ + /* std::to_string(std::get(right)); */ + /* } else if (std::holds_alternative(left) && */ + /* std::holds_alternative(right)) { */ + /* return std::get(left) + */ + /* std::to_string(std::get(right)); */ + /* } else if (std::holds_alternative(left) && */ + /* std::holds_alternative(right)) { */ + /* return std::to_string(std::get(left)) + */ + /* std::get(right); */ + /* } else if (std::holds_alternative(left) && */ + /* std::holds_alternative(right)) { */ + /* return std::get(left) + std::get(right); */ + /* } else if (std::holds_alternative(left) && */ + /* std::holds_alternative(right)) { */ + /* return std::get(left) + std::get(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(right)) { + return -std::get(right); + } else if (std::holds_alternative(right)) { + return -std::get(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 args; + args.reserve(expr->args.size()); + + for (auto &&arg : expr->args) { + args.emplace_back(arg->accept(this)); + } + + if (std::holds_alternative>(callee)) { + auto function = std::get>(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>(callee)) { + auto clas = std::get>(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>(instance)) { + return std::get>(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>(instance)) { + LBPLType value = expr->value->accept(this); + std::get>(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->accept(this); } + +LBPLType Interpreter::evaluate(std::unique_ptr &expr) { + return expr->accept(this); +} + +LBPLType Interpreter::performBinaryOperation(std::shared_ptr &op, + const LBPLType &left, + const LBPLType &right) { + auto performIntOp = [](int l, int r, + std::shared_ptr &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 &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 &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; + using R = std::decay_t; + + if constexpr (std::is_same_v && std::is_same_v) { + return performIntOp(l, r, op); + } else if constexpr (std::is_same_v && + std::is_same_v) { + return performDoubleOp(l, r, op); + } else if constexpr ((std::is_same_v && + std::is_same_v) || + (std::is_same_v && + std::is_same_v)) { + // Handle int-to-string or string-to-int concatenation + std::string leftStr = std::holds_alternative(left) + ? std::to_string(std::get(left)) + : std::get(left); + std::string rightStr = std::holds_alternative(right) + ? std::to_string(std::get(right)) + : std::get(right); + if (op->type == TokenType::Plus) { + return leftStr + rightStr; + } else { + throw RuntimeError(op.get(), "Unsupported binary operation."); + } + } else if constexpr ((std::is_same_v && + std::is_same_v) || + (std::is_same_v && + std::is_same_v)) { + // Handle double-to-string or string-to-double concatenation + std::string leftStr = std::holds_alternative(left) + ? std::to_string(std::get(left)) + : std::get(left); + std::string rightStr = std::holds_alternative(right) + ? std::to_string(std::get(right)) + : std::get(right); + if (op->type == TokenType::Plus) { + return leftStr + rightStr; + } else { + throw RuntimeError(op.get(), "Unsupported binary operation."); + } + } else if constexpr (std::is_same_v && + std::is_same_v) { + return performStringOp(l, r, op); + } else { + throw RuntimeError(op.get(), "Unsupported binary operation."); + } + }, + left, right); +} + +/* template */ +/* 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> &body, + std::shared_ptr &&env) { + executeBlock(body, env); +} + +void Interpreter::executeBlock(std::vector> &body, + std::shared_ptr &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(value)) { + return std::get(value) != nullptr; + } else if (std::holds_alternative(value)) { + return std::get(value); + } else if (std::holds_alternative(value)) { + return std::get(value) == 0; + } else if (std::holds_alternative(value)) { + return std::get(value) == 0; + } + + return false; +} + +bool Interpreter::isTruthy(LBPLType &&value) { return isTruthy(value); } + +LBPLType Interpreter::lookupVariable(std::shared_ptr &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 index 0000000..098bd9a --- /dev/null +++ b/src/lexer.cpp @@ -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 Lexer::makeToken(TokenType type, + std::string value) { + if (value != "") { + return std::make_shared(type, value, line, + start - currentLine.begin(), filename); + } + + return std::make_shared(type, std::string(start, current), line, + start - currentLine.begin(), filename); +} + +std::shared_ptr Lexer::makeNumberToken() { + while (isDigit(peek())) { + advance(); + } + + if (peek() == '.' && isDigit(peekNext())) { + advance(); + while (isDigit(peek())) { + advance(); + } + } + + return makeToken(TokenType::Number); +} + +std::shared_ptr Lexer::makeIdentifierToken() { + while (isAlpha(peek()) || isDigit(peek()) || peek() < 0) { + advance(); + } + + return makeToken(isIdentifierOrKeywork()); +} + +std::shared_ptr Lexer::makeErrorToken(std::string msg) { + hadError = true; + return std::make_shared(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 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 index 0000000..e2de6ed --- /dev/null +++ b/src/main.cpp @@ -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> 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 index 0000000..7edf294 --- /dev/null +++ b/src/parser.cpp @@ -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> Parser::parse() { + std::vector> 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 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> Parser::importStmt() { + std::shared_ptr 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 Parser::varDecl() { + int line = lexer->getLine(), col = lexer->getColumn(); + + std::shared_ptr name = + consume("Expected variable name after 'let' keyword but instead got '" + + current->lexeme + "'.", + TokenType::Identifier); + + std::unique_ptr 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(line, col, lexer->getFilename(), name, + value); +} + +std::unique_ptr Parser::functionDecl(const std::string &kind) { + std::vector> args; + int line = lexer->getLine(), col = lexer->getColumn(); + std::shared_ptr 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 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(line, col, lexer->getFilename(), name, args, + stmtSequence()); +} + +std::unique_ptr Parser::classDecl() { + int line = lexer->getLine(), col = lexer->getColumn(); + + std::shared_ptr name = + consume("Expected class name but instead got '" + current->lexeme + "'.", + TokenType::Identifier); + + std::unique_ptr superclass; + if (match(TokenType::Colon)) { + std::shared_ptr supername = + consume("Expected superclass name.", TokenType::Identifier); + superclass = std::make_unique(line, col, lexer->getFilename(), + supername); + } + + auto clas = std::make_unique(line, col, lexer->getFilename(), name, + superclass, + std::vector>()); + 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 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 Parser::scopedStmt() { + int line = lexer->getLine(), col = lexer->getColumn(); + return std::make_unique(line, col, lexer->getFilename(), + stmtSequence()); +} + +std::unique_ptr Parser::ifStmt() { + int line = lexer->getLine(), col = lexer->getColumn(); + std::unique_ptr cond = expression(); + std::unique_ptr trueBranch = statement(); + std::unique_ptr falseBranch; + + if (match(TokenType::Else)) { + falseBranch = statement(); + } + + return std::make_unique(line, col, lexer->getFilename(), cond, + trueBranch, falseBranch); +} + +std::unique_ptr Parser::whileStmt() { + int line = lexer->getLine(), col = lexer->getColumn(); + + auto condition = expression(); + auto body = statement(); + return std::make_unique(line, col, lexer->getFilename(), condition, + body); +} + +std::unique_ptr Parser::loopStmt() { + int line = lexer->getLine(), col = lexer->getColumn(); + + std::unique_ptr cond = std::make_unique( + line, col, lexer->getFilename(), + std::make_shared(TokenType::True, "true", previous->line, + previous->column, previous->filename)); + return std::make_unique(line, col, lexer->getFilename(), cond, + statement()); +} + +std::unique_ptr 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 initializer; + if (match(TokenType::Let)) { + initializer = varDecl(); + } else if (!match(TokenType::Semicolon)) { + initializer = expressionStmt(); + } + + std::unique_ptr cond; + if (!check(TokenType::Semicolon)) { + cond = expression(); + } + consume("Expected ';' after loop condition but instead got '" + + current->lexeme + "'.", + TokenType::Semicolon); + + std::unique_ptr increment; + if (!check(TokenType::RightParen)) { + increment = expression(); + } + consume("Expected ')' after loop clauses but instead got '" + + current->lexeme + "'.", + TokenType::RightParen); + + std::unique_ptr body = statement(); + + if (!cond) { + cond = std::make_unique( + line, col, lexer->getFilename(), + std::make_shared(TokenType::True, "true", previous->line, + previous->column, previous->filename)); + } + + return std::make_unique(line, col, lexer->getFilename(), initializer, + cond, increment, body); +} + +std::unique_ptr Parser::returnStmt() { + int line = lexer->getLine(), col = lexer->getColumn(); + std::unique_ptr value; + + if (!check(TokenType::Semicolon)) { + value = expression(); + } + CONSUME_SEMICOLON("return"); + + return std::make_unique(line, col, lexer->getFilename(), value); +} + +std::unique_ptr Parser::expressionStmt() { + int line = lexer->getLine(), col = lexer->getColumn(); + + auto expr = + std::make_unique(line, col, lexer->getFilename(), expression()); + CONSUME_SEMICOLON("expression"); + + return expr; +} + +std::unique_ptr Parser::expression() { + if (match(TokenType::Break)) { + int line = lexer->getLine(), col = lexer->getColumn(); + return std::make_unique(line, col, lexer->getFilename()); + } else if (match(TokenType::Break)) { + int line = lexer->getLine(), col = lexer->getColumn(); + return std::make_unique(line, col, lexer->getFilename()); + } + + return assignment(); +} + +std::unique_ptr Parser::assignment() { + std::unique_ptr left = orExpr(); + int line = lexer->getLine(), col = lexer->getColumn(); + + if (match(TokenType::Equal)) { + std::unique_ptr value = assignment(); + if (auto var = dynamic_cast(left.get())) { + return std::make_unique(line, col, lexer->getFilename(), + var->variable, value); + } else if (auto get = dynamic_cast(left.get())) { + return std::make_unique(line, col, lexer->getFilename(), + get->instance, get->field, value); + } + + throw SyntaxError(value.get(), "Invalid assignment value."); + } else if (match(TokenType::Question)) { + std::unique_ptr trueExpr = assignment(); + consume("Expected ':' between ternary expressions but instead got '" + + current->lexeme + "'.", + TokenType::Colon); + + return std::make_unique(line, col, lexer->getFilename(), left, + trueExpr, assignment()); + } + + return left; +} + +std::unique_ptr Parser::orExpr() { + std::unique_ptr left = andExpr(); + int line = lexer->getLine(), col = lexer->getColumn(); + + while (match(TokenType::Or)) { + std::shared_ptr op = previous; + + left = std::make_unique(line, col, lexer->getFilename(), left, + andExpr(), op); + } + + return left; +} + +std::unique_ptr Parser::andExpr() { + std::unique_ptr left = equality(); + int line = lexer->getLine(), col = lexer->getColumn(); + + while (match(TokenType::And)) { + std::shared_ptr op = previous; + + left = std::make_unique(line, col, lexer->getFilename(), left, + equality(), op); + } + + return left; +} + +std::unique_ptr Parser::equality() { + std::unique_ptr left = comparison(); + int line = lexer->getLine(), col = lexer->getColumn(); + + while (match(TokenType::EqualEqual, TokenType::BangEqual)) { + std::shared_ptr op = previous; + + left = std::make_unique(line, col, lexer->getFilename(), left, + comparison(), op); + } + + return left; +} + +std::unique_ptr Parser::comparison() { + std::unique_ptr left = term(); + int line = lexer->getLine(), col = lexer->getColumn(); + + while (match(TokenType::Greater, TokenType::GreaterEqual, TokenType::Less, + TokenType::LessEqual)) { + std::shared_ptr op = previous; + + left = std::make_unique(line, col, lexer->getFilename(), left, + term(), op); + } + + return left; +} + +std::unique_ptr Parser::term() { + std::unique_ptr left = factor(); + int line = lexer->getLine(), col = lexer->getColumn(); + + while (match(TokenType::Plus, TokenType::Minus, TokenType::ModOp)) { + std::shared_ptr op = previous; + + left = std::make_unique(line, col, lexer->getFilename(), left, + factor(), op); + } + + return left; +} + +std::unique_ptr Parser::factor() { + std::unique_ptr left = unary(); + int line = lexer->getLine(), col = lexer->getColumn(); + + while (match(TokenType::Star, TokenType::Slash)) { + std::shared_ptr op = previous; + + left = std::make_unique(line, col, lexer->getFilename(), left, + unary(), op); + } + + return left; +} + +std::unique_ptr Parser::unary() { + int line = lexer->getLine(), col = lexer->getColumn(); + if (match(TokenType::Bang, TokenType::Minus)) { + std::shared_ptr op = previous; + return std::make_unique(line, col, lexer->getFilename(), unary(), + op); + } + + return call(); +} + +std::unique_ptr Parser::call() { + std::unique_ptr expr = primary(); + int line = lexer->getLine(), col = lexer->getColumn(); + + while (1) { + if (match(TokenType::LeftParen)) { + std::vector> 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(line, col, lexer->getFilename(), expr, + args); + } else if (match(TokenType::Dot)) { + std::shared_ptr prop = + consume("Expected class property or method but instead got '" + + current->lexeme + "'.", + TokenType::Identifier); + expr = std::make_unique(line, col, lexer->getFilename(), + expr, prop); + } else { + break; + } + } + + return expr; +} + +std::unique_ptr Parser::primary() { + int line = lexer->getLine(), col = lexer->getColumn(); + + if (match(TokenType::Super)) { + consume("Expected '.' after super keyword.", TokenType::Dot); + std::shared_ptr field = + consume("Expected superclass field.", TokenType::Identifier); + + return std::make_unique(line, col, lexer->getFilename(), field); + } else if (match(TokenType::This)) { + return std::make_unique(line, col, lexer->getFilename()); + } else if (match(TokenType::Identifier)) { + return std::make_unique(line, col, lexer->getFilename(), + previous); + } else if (match(TokenType::Number, TokenType::Char, TokenType::String, + TokenType::True, TokenType::False, TokenType::Nil)) { + return std::make_unique(line, col, lexer->getFilename(), + previous); + } else if (match(TokenType::LeftParen)) { + std::unique_ptr expr = expression(); + consume("Missing closing parenthesis ')' after expression.", + TokenType::RightParen); + return std::make_unique(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 +bool Parser::check(const TokenTypes &...types) { + return ((current->type == types) || ...); +} + +template +bool Parser::match(const TokenTypes &...types) { + if (!(check(types) || ...)) { + return false; + } + + advance(); + return true; +} + +std::shared_ptr Parser::advance() { + previous = current; + + if (isAtEnd()) { + return current; + } + + current = lexer->getNextToken(); + if (lexer->hadError) { + throw SyntaxError(current.get(), current->lexeme); + } + return previous; +} + +template +std::shared_ptr Parser::consume(const std::string &msg, + const TokenTypes &...types) { + if (!(check(types) || ...)) { + throw SyntaxError(current.get(), msg); + } + + std::shared_ptr res = current; + advance(); + return res; +} + +std::vector> Parser::stmtSequence() { + std::vector> 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 index 0000000..90bc24f --- /dev/null +++ b/src/resolver.cpp @@ -0,0 +1,258 @@ +#include "resolver.h" + +void Resolver::resolve(std::vector> &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 &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(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 index 0000000..da079da --- /dev/null +++ b/src/runtime_error.cpp @@ -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 index 0000000..fa7ef68 --- /dev/null +++ b/src/syntax_error.cpp @@ -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; +} -- 2.52.0