├── .gitignore ├── LICENSE ├── README.md ├── chapter10 ├── AstPrinter.h ├── AstPrinterDriver.cpp ├── Environment.h ├── Error.h ├── Expr.h ├── GenerateAst.cpp ├── Interpreter.h ├── Lox.cpp ├── LoxCallable.h ├── LoxFunction.cpp ├── LoxFunction.h ├── LoxReturn.h ├── Makefile ├── Parser.h ├── RuntimeError.h ├── Scanner.h ├── Stmt.h ├── Token.h ├── TokenType.h └── tests │ ├── test-control-flow.lox │ ├── test-control-flow.lox.expected │ ├── test-control-flow2.lox │ ├── test-control-flow2.lox.expected │ ├── test-functions.lox │ ├── test-functions.lox.expected │ ├── test-functions2.lox │ ├── test-functions2.lox.expected │ ├── test-functions3.lox │ ├── test-functions3.lox.expected │ ├── test-functions4.lox │ ├── test-functions4.lox.expected │ ├── test-statements.lox │ ├── test-statements.lox.expected │ ├── test-statements2.lox │ ├── test-statements2.lox.expected │ ├── test-statements3.lox │ ├── test-statements3.lox.expected │ ├── test-statements4.lox │ ├── test-statements4.lox.expected │ ├── test-statements5.lox │ ├── test-statements5.lox.expected │ ├── test-statements6.lox │ └── test-statements6.lox.expected ├── chapter11 ├── AstPrinter.h ├── AstPrinterDriver.cpp ├── Environment.h ├── Error.h ├── Expr.h ├── GenerateAst.cpp ├── Interpreter.h ├── Lox.cpp ├── LoxCallable.h ├── LoxFunction.cpp ├── LoxFunction.h ├── LoxReturn.h ├── Makefile ├── Parser.h ├── Resolver.h ├── RuntimeError.h ├── Scanner.h ├── Stmt.h ├── Token.h ├── TokenType.h └── tests │ ├── test-control-flow.lox │ ├── test-control-flow.lox.expected │ ├── test-control-flow2.lox │ ├── test-control-flow2.lox.expected │ ├── test-functions.lox │ ├── test-functions.lox.expected │ ├── test-functions2.lox │ ├── test-functions2.lox.expected │ ├── test-functions3.lox │ ├── test-functions3.lox.expected │ ├── test-functions4.lox │ ├── test-functions4.lox.expected │ ├── test-resolving.lox │ ├── test-resolving.lox.expected │ ├── test-resolving2.lox │ ├── test-resolving2.lox.expected │ ├── test-resolving3.lox │ ├── test-resolving3.lox.expected │ ├── test-resolving4.lox │ ├── test-resolving4.lox.expected │ ├── test-statements.lox │ ├── test-statements.lox.expected │ ├── test-statements2.lox │ ├── test-statements2.lox.expected │ ├── test-statements3.lox │ ├── test-statements3.lox.expected │ ├── test-statements4.lox │ ├── test-statements4.lox.expected │ ├── test-statements5.lox │ ├── test-statements5.lox.expected │ ├── test-statements6.lox │ └── test-statements6.lox.expected ├── chapter12 ├── AstPrinter.h ├── AstPrinterDriver.cpp ├── Environment.h ├── Error.h ├── Expr.h ├── GenerateAst.cpp ├── Interpreter.h ├── Lox.cpp ├── LoxCallable.h ├── LoxClass.cpp ├── LoxClass.h ├── LoxFunction.cpp ├── LoxFunction.h ├── LoxInstance.cpp ├── LoxInstance.h ├── LoxReturn.h ├── Makefile ├── Parser.h ├── Resolver.h ├── RuntimeError.h ├── Scanner.h ├── Stmt.h ├── Token.h ├── TokenType.h └── tests │ ├── test-classes.lox │ ├── test-classes.lox.expected │ ├── test-classes10.lox │ ├── test-classes10.lox.expected │ ├── test-classes11.lox │ ├── test-classes11.lox.expected │ ├── test-classes12.lox │ ├── test-classes12.lox.expected │ ├── test-classes13.lox │ ├── test-classes13.lox.expected │ ├── test-classes2.lox │ ├── test-classes2.lox.expected │ ├── test-classes3.lox │ ├── test-classes3.lox.expected │ ├── test-classes4.lox │ ├── test-classes4.lox.expected │ ├── test-classes5.lox │ ├── test-classes5.lox.expected │ ├── test-classes6.lox │ ├── test-classes6.lox.expected │ ├── test-classes7.lox │ ├── test-classes7.lox.expected │ ├── test-classes8.lox │ ├── test-classes8.lox.expected │ ├── test-classes9.lox │ ├── test-classes9.lox.expected │ ├── test-control-flow.lox │ ├── test-control-flow.lox.expected │ ├── test-control-flow2.lox │ ├── test-control-flow2.lox.expected │ ├── test-functions.lox │ ├── test-functions.lox.expected │ ├── test-functions2.lox │ ├── test-functions2.lox.expected │ ├── test-functions3.lox │ ├── test-functions3.lox.expected │ ├── test-functions4.lox │ ├── test-functions4.lox.expected │ ├── test-resolving.lox │ ├── test-resolving.lox.expected │ ├── test-resolving2.lox │ ├── test-resolving2.lox.expected │ ├── test-resolving3.lox │ ├── test-resolving3.lox.expected │ ├── test-resolving4.lox │ ├── test-resolving4.lox.expected │ ├── test-statements.lox │ ├── test-statements.lox.expected │ ├── test-statements2.lox │ ├── test-statements2.lox.expected │ ├── test-statements3.lox │ ├── test-statements3.lox.expected │ ├── test-statements4.lox │ ├── test-statements4.lox.expected │ ├── test-statements5.lox │ ├── test-statements5.lox.expected │ ├── test-statements6.lox │ └── test-statements6.lox.expected ├── chapter13 ├── AstPrinter.h ├── AstPrinterDriver.cpp ├── Environment.h ├── Error.h ├── Expr.h ├── GenerateAst.cpp ├── Interpreter.h ├── Lox.cpp ├── LoxCallable.h ├── LoxClass.cpp ├── LoxClass.h ├── LoxFunction.cpp ├── LoxFunction.h ├── LoxInstance.cpp ├── LoxInstance.h ├── LoxReturn.h ├── Makefile ├── Parser.h ├── Resolver.h ├── RuntimeError.h ├── Scanner.h ├── Stmt.h ├── Token.h ├── TokenType.h └── tests │ ├── test-classes.lox │ ├── test-classes.lox.expected │ ├── test-classes10.lox │ ├── test-classes10.lox.expected │ ├── test-classes11.lox │ ├── test-classes11.lox.expected │ ├── test-classes12.lox │ ├── test-classes12.lox.expected │ ├── test-classes13.lox │ ├── test-classes13.lox.expected │ ├── test-classes2.lox │ ├── test-classes2.lox.expected │ ├── test-classes3.lox │ ├── test-classes3.lox.expected │ ├── test-classes4.lox │ ├── test-classes4.lox.expected │ ├── test-classes5.lox │ ├── test-classes5.lox.expected │ ├── test-classes6.lox │ ├── test-classes6.lox.expected │ ├── test-classes7.lox │ ├── test-classes7.lox.expected │ ├── test-classes8.lox │ ├── test-classes8.lox.expected │ ├── test-classes9.lox │ ├── test-classes9.lox.expected │ ├── test-control-flow.lox │ ├── test-control-flow.lox.expected │ ├── test-control-flow2.lox │ ├── test-control-flow2.lox.expected │ ├── test-functions.lox │ ├── test-functions.lox.expected │ ├── test-functions2.lox │ ├── test-functions2.lox.expected │ ├── test-functions3.lox │ ├── test-functions3.lox.expected │ ├── test-functions4.lox │ ├── test-functions4.lox.expected │ ├── test-inheritance.lox │ ├── test-inheritance.lox.expected │ ├── test-inheritance2.lox │ ├── test-inheritance2.lox.expected │ ├── test-inheritance3.lox │ ├── test-inheritance3.lox.expected │ ├── test-inheritance4.lox │ ├── test-inheritance4.lox.expected │ ├── test-inheritance5.lox │ ├── test-inheritance5.lox.expected │ ├── test-inheritance6.lox │ ├── test-inheritance6.lox.expected │ ├── test-inheritance7.lox │ ├── test-inheritance7.lox.expected │ ├── test-resolving.lox │ ├── test-resolving.lox.expected │ ├── test-resolving2.lox │ ├── test-resolving2.lox.expected │ ├── test-resolving3.lox │ ├── test-resolving3.lox.expected │ ├── test-resolving4.lox │ ├── test-resolving4.lox.expected │ ├── test-statements.lox │ ├── test-statements.lox.expected │ ├── test-statements2.lox │ ├── test-statements2.lox.expected │ ├── test-statements3.lox │ ├── test-statements3.lox.expected │ ├── test-statements4.lox │ ├── test-statements4.lox.expected │ ├── test-statements5.lox │ ├── test-statements5.lox.expected │ ├── test-statements6.lox │ └── test-statements6.lox.expected ├── chapter4 ├── Error.h ├── Lox.cpp ├── Makefile ├── Scanner.h ├── Token.h ├── TokenType.h └── tests │ ├── test-lexing.lox │ ├── test-lexing.lox.expected │ ├── test-lexing2.lox │ └── test-lexing2.lox.expected ├── chapter5 ├── AstPrinter.h ├── AstPrinterDriver.cpp ├── Error.h ├── Expr.h ├── GenerateAst.cpp ├── Lox.cpp ├── Makefile ├── Scanner.h ├── Token.h ├── TokenType.h └── tests │ ├── test-lexing.lox │ ├── test-lexing.lox.expected │ ├── test-lexing2.lox │ └── test-lexing2.lox.expected ├── chapter6 ├── AstPrinter.h ├── AstPrinterDriver.cpp ├── Error.h ├── Expr.h ├── GenerateAst.cpp ├── Lox.cpp ├── Makefile ├── Parser.h ├── Scanner.h ├── Token.h ├── TokenType.h └── tests │ ├── test-parsing.lox │ └── test-parsing.lox.expected ├── chapter7 ├── AstPrinter.h ├── AstPrinterDriver.cpp ├── Error.h ├── Expr.h ├── GenerateAst.cpp ├── Interpreter.h ├── Lox.cpp ├── Makefile ├── Parser.h ├── RuntimeError.h ├── Scanner.h ├── Token.h ├── TokenType.h └── tests │ ├── test-expressions.lox │ ├── test-expressions.lox.expected │ ├── test-expressions2.lox │ └── test-expressions2.lox.expected ├── chapter8 ├── AstPrinter.h ├── AstPrinterDriver.cpp ├── Environment.h ├── Error.h ├── Expr.h ├── GenerateAst.cpp ├── Interpreter.h ├── Lox.cpp ├── Makefile ├── Parser.h ├── RuntimeError.h ├── Scanner.h ├── Stmt.h ├── Token.h ├── TokenType.h ├── challenge2 │ ├── AstPrinter.h │ ├── AstPrinterDriver.cpp │ ├── Environment.h │ ├── Error.h │ ├── Expr.h │ ├── GenerateAst.cpp │ ├── Interpreter.h │ ├── Lox.cpp │ ├── Makefile │ ├── Parser.h │ ├── RuntimeError.h │ ├── Scanner.h │ ├── Stmt.h │ ├── Token.h │ ├── TokenType.h │ └── tests │ │ ├── test-challenge2.lox │ │ ├── test-challenge2.lox.expected │ │ ├── test-statements.lox │ │ ├── test-statements.lox.expected │ │ ├── test-statements2.lox │ │ ├── test-statements2.lox.expected │ │ ├── test-statements3.lox │ │ ├── test-statements3.lox.expected │ │ ├── test-statements4.lox │ │ ├── test-statements4.lox.expected │ │ ├── test-statements5.lox │ │ ├── test-statements5.lox.expected │ │ ├── test-statements6.lox │ │ └── test-statements6.lox.expected └── tests │ ├── test-statements.lox │ ├── test-statements.lox.expected │ ├── test-statements2.lox │ ├── test-statements2.lox.expected │ ├── test-statements3.lox │ ├── test-statements3.lox.expected │ ├── test-statements4.lox │ ├── test-statements4.lox.expected │ ├── test-statements5.lox │ ├── test-statements5.lox.expected │ ├── test-statements6.lox │ └── test-statements6.lox.expected └── chapter9 ├── AstPrinter.h ├── AstPrinterDriver.cpp ├── Environment.h ├── Error.h ├── Expr.h ├── GenerateAst.cpp ├── Interpreter.h ├── Lox.cpp ├── Makefile ├── Parser.h ├── RuntimeError.h ├── Scanner.h ├── Stmt.h ├── Token.h ├── TokenType.h └── tests ├── test-control-flow.lox ├── test-control-flow.lox.expected ├── test-control-flow2.lox ├── test-control-flow2.lox.expected ├── test-statements.lox ├── test-statements.lox.expected ├── test-statements2.lox ├── test-statements2.lox.expected ├── test-statements3.lox ├── test-statements3.lox.expected ├── test-statements4.lox ├── test-statements4.lox.expected ├── test-statements5.lox ├── test-statements5.lox.expected ├── test-statements6.lox └── test-statements6.lox.expected /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | *.d 3 | *.o 4 | jlox 5 | generate_ast 6 | ast_printer -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mike Castillo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /chapter10/AstPrinter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "Expr.h" 11 | 12 | class AstPrinter: public ExprVisitor { 13 | public: 14 | std::string print(std::shared_ptr expr) { 15 | return std::any_cast(expr->accept(*this)); 16 | } 17 | 18 | std::any visitBinaryExpr(std::shared_ptr expr) override { 19 | return parenthesize(expr->op.lexeme, 20 | expr->left, expr->right); 21 | } 22 | 23 | std::any visitGroupingExpr( 24 | std::shared_ptr expr) override { 25 | return parenthesize("group", expr->expression); 26 | } 27 | 28 | std::any visitLiteralExpr(std::shared_ptr expr) override { 29 | auto& value_type = expr->value.type(); 30 | 31 | if (value_type == typeid(nullptr)) { 32 | return "nil"; 33 | } else if (value_type == typeid(std::string)) { 34 | return std::any_cast(expr->value); 35 | } else if (value_type == typeid(double)) { 36 | return std::to_string(std::any_cast(expr->value)); 37 | } else if (value_type == typeid(bool)) { 38 | return std::any_cast(expr->value) ? "true" : "false"; 39 | } 40 | 41 | return "Error in visitLiteralExpr: literal type not recognized."; 42 | } 43 | 44 | std::any visitUnaryExpr(std::shared_ptr expr) override { 45 | return parenthesize(expr->op.lexeme, expr->right); 46 | } 47 | 48 | private: 49 | template 50 | std::string parenthesize(std::string_view name, E... expr) 51 | { 52 | assert((... && std::is_same_v>)); 53 | 54 | std::ostringstream builder; 55 | 56 | builder << "(" << name; 57 | ((builder << " " << print(expr)), ...); 58 | builder << ")"; 59 | 60 | return builder.str(); 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /chapter10/AstPrinterDriver.cpp: -------------------------------------------------------------------------------- 1 | #include "AstPrinter.h" 2 | 3 | int main(int argc, char* argv[]) { 4 | std::shared_ptr expression = std::make_shared( 5 | std::make_shared( 6 | Token{MINUS, "-", nullptr, 1}, 7 | std::make_shared(123.) 8 | ), 9 | Token{STAR, "*", nullptr, 1}, 10 | std::make_shared( 11 | std::make_shared(45.67))); 12 | 13 | std::cout << AstPrinter{}.print(expression) << "\n"; 14 | } -------------------------------------------------------------------------------- /chapter10/Environment.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include // less 5 | #include 6 | #include 7 | #include 8 | #include // std::move 9 | #include "Error.h" 10 | #include "Token.h" 11 | 12 | class Environment: public std::enable_shared_from_this { 13 | friend class Interpreter; 14 | 15 | std::shared_ptr enclosing; 16 | std::map values; 17 | 18 | public: 19 | Environment() 20 | : enclosing{nullptr} 21 | {} 22 | 23 | Environment(std::shared_ptr enclosing) 24 | : enclosing{std::move(enclosing)} 25 | {} 26 | 27 | std::any get(const Token& name) { 28 | auto elem = values.find(name.lexeme); 29 | if (elem != values.end()) { 30 | return elem->second; 31 | } 32 | 33 | if (enclosing != nullptr) return enclosing->get(name); 34 | 35 | throw RuntimeError(name, 36 | "Undefined variable '" + name.lexeme + "'."); 37 | } 38 | 39 | void assign(const Token& name, std::any value) { 40 | auto elem = values.find(name.lexeme); 41 | if (elem != values.end()) { 42 | elem->second = std::move(value); 43 | return; 44 | } 45 | 46 | if (enclosing != nullptr) { 47 | enclosing->assign(name, std::move(value)); 48 | return; 49 | } 50 | 51 | throw RuntimeError(name, 52 | "Undefined variable '" + name.lexeme + "'."); 53 | } 54 | 55 | void define(const std::string& name, std::any value) { 56 | values[name] = std::move(value); 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /chapter10/Error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "RuntimeError.h" 6 | #include "Token.h" 7 | 8 | inline bool hadError = false; 9 | inline bool hadRuntimeError = false; 10 | 11 | static void report(int line, std::string_view where, 12 | std::string_view message) { 13 | std::cerr << 14 | "[line " << line << "] Error" << where << ": " << message << 15 | "\n"; 16 | hadError = true; 17 | } 18 | 19 | void error(const Token& token, std::string_view message) { 20 | if (token.type == END_OF_FILE) { 21 | report(token.line, " at end", message); 22 | } else { 23 | report(token.line, " at '" + token.lexeme + "'", message); 24 | } 25 | } 26 | 27 | void error(int line, std::string_view message) { 28 | report(line, "", message); 29 | } 30 | 31 | void runtimeError(const RuntimeError& error) { 32 | std::cerr << error.what() << 33 | "\n[line " << error.token.line << "]\n"; 34 | hadRuntimeError = true; 35 | } 36 | -------------------------------------------------------------------------------- /chapter10/Lox.cpp: -------------------------------------------------------------------------------- 1 | #include // std::strerror 2 | #include // readFile 3 | #include // std::getline 4 | #include 5 | #include 6 | #include "Error.h" 7 | #include "Interpreter.h" 8 | #include "Parser.h" 9 | #include "Scanner.h" 10 | 11 | // It's not good practice to include .cpp files, but in our case it 12 | // allows us to lay out the files similarly to the Java code while 13 | // avoiding circular dependencies. 14 | #include "LoxFunction.cpp" // Chapter 10 - Functions 15 | 16 | std::string readFile(std::string_view path) { 17 | std::ifstream file{path.data(), std::ios::in | std::ios::binary | 18 | std::ios::ate}; 19 | if (!file) { 20 | std::cerr << "Failed to open file " << path << ": " 21 | << std::strerror(errno) << "\n"; 22 | std::exit(74); 23 | }; 24 | 25 | std::string contents; 26 | contents.resize(file.tellg()); 27 | 28 | file.seekg(0, std::ios::beg); 29 | file.read(contents.data(), contents.size()); 30 | 31 | return contents; 32 | } 33 | 34 | Interpreter interpreter{}; 35 | 36 | void run(std::string_view source) { 37 | Scanner scanner {source}; 38 | std::vector tokens = scanner.scanTokens(); 39 | Parser parser{tokens}; 40 | std::vector> statements = parser.parse(); 41 | 42 | // Stop if there was a syntax error. 43 | if (hadError) return; 44 | 45 | // Stop if there was a resolution error. 46 | if (hadError) return; 47 | 48 | interpreter.interpret(statements); 49 | } 50 | 51 | void runFile(std::string_view path) { 52 | std::string contents = readFile(path); 53 | run(contents); 54 | 55 | // Indicate an error in the exit code. 56 | if (hadError) std::exit(65); 57 | if (hadRuntimeError) std::exit(70); 58 | } 59 | 60 | void runPrompt() { 61 | for (;;) { 62 | std::cout << "> "; 63 | std::string line; 64 | if (!std::getline(std::cin, line)) break; 65 | run(line); 66 | hadError = false; 67 | } 68 | } 69 | 70 | int main(int argc, char* argv[]) { 71 | if (argc > 2) { 72 | std::cout << "Usage: jlox [script]\n"; 73 | std::exit(64); 74 | } else if (argc == 2) { 75 | runFile(argv[1]); 76 | } else { 77 | runPrompt(); 78 | } 79 | } -------------------------------------------------------------------------------- /chapter10/LoxCallable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class Interpreter; 8 | 9 | class LoxCallable { 10 | public: 11 | virtual int arity() = 0; 12 | virtual std::any call(Interpreter& interpreter, 13 | std::vector arguments) = 0; 14 | virtual std::string toString() = 0; 15 | virtual ~LoxCallable() = default; 16 | }; -------------------------------------------------------------------------------- /chapter10/LoxFunction.cpp: -------------------------------------------------------------------------------- 1 | #include "LoxFunction.h" 2 | #include // std::move 3 | #include "Environment.h" 4 | #include "Interpreter.h" 5 | #include "Stmt.h" 6 | 7 | // LoxFunction::LoxFunction(std::shared_ptr declaration) 8 | // : declaration{std::move(declaration)} 9 | // {} 10 | 11 | LoxFunction::LoxFunction(std::shared_ptr declaration, 12 | std::shared_ptr closure) 13 | : closure{std::move(closure)}, declaration{std::move(declaration)} 14 | {} 15 | 16 | std::string LoxFunction::toString() { 17 | return "name.lexeme + ">"; 18 | } 19 | 20 | int LoxFunction::arity() { 21 | return declaration->params.size(); 22 | } 23 | 24 | std::any LoxFunction::call(Interpreter& interpreter, 25 | std::vector arguments) { 26 | // auto environment = std::make_shared( 27 | // interpreter.globals); 28 | auto environment = std::make_shared(closure); 29 | for (int i = 0; i < declaration->params.size(); ++i) { 30 | environment->define(declaration->params[i].lexeme, 31 | arguments[i]); 32 | } 33 | 34 | // interpreter.executeBlock(declaration->body, environment); 35 | try { 36 | interpreter.executeBlock(declaration->body, environment); 37 | } catch (LoxReturn returnValue) { 38 | return returnValue.value; 39 | } 40 | 41 | return nullptr; 42 | } 43 | -------------------------------------------------------------------------------- /chapter10/LoxFunction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "LoxCallable.h" 8 | 9 | class Environment; 10 | class Function; 11 | 12 | class LoxFunction: public LoxCallable { 13 | std::shared_ptr declaration; 14 | std::shared_ptr closure; 15 | 16 | public: 17 | // LoxFunction(std::shared_ptr declaration); 18 | LoxFunction(std::shared_ptr declaration, 19 | std::shared_ptr closure); 20 | std::string toString() override; 21 | int arity() override; 22 | std::any call(Interpreter& interpreter, 23 | std::vector arguments) override; 24 | }; 25 | -------------------------------------------------------------------------------- /chapter10/LoxReturn.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct LoxReturn { 6 | const std::any value; 7 | }; -------------------------------------------------------------------------------- /chapter10/Makefile: -------------------------------------------------------------------------------- 1 | CXX := g++ 2 | CXXFLAGS := -ggdb -std=c++17 3 | CPPFLAGS := -MMD 4 | 5 | COMPILE := $(CXX) $(CXXFLAGS) $(CPPFLAGS) 6 | 7 | SRCS := AstPrinterDriver.cpp GenerateAst.cpp Lox.cpp 8 | DEPS := $(SRCS:.cpp=.d) 9 | 10 | 11 | jlox: Expr.h Stmt.h Lox.o 12 | @$(COMPILE) Lox.o -o $@ 13 | 14 | 15 | ast_printer: Expr.h Stmt.h AstPrinterDriver.o 16 | @$(COMPILE) AstPrinterDriver.o -o $@ 17 | 18 | 19 | generate_ast: GenerateAst.o 20 | @$(COMPILE) $< -o $@ 21 | 22 | 23 | Expr.h Stmt.h: generate_ast 24 | @./generate_ast . 25 | 26 | 27 | .PHONY: clean 28 | clean: 29 | rm -f *.d *.o ast_printer generate_ast jlox 30 | 31 | 32 | -include $(DEPS) 33 | 34 | 35 | define make_test 36 | .PHONY: $(1) 37 | $(1): 38 | @make jlox >/dev/null 39 | @echo "testing jlox with $(1).lox ..." 40 | @./jlox tests/$(1).lox | diff -u --color tests/$(1).lox.expected -; 41 | endef 42 | 43 | 44 | TESTS = \ 45 | test-statements \ 46 | test-statements2 \ 47 | test-statements3 \ 48 | test-statements4 \ 49 | test-statements5 \ 50 | test-statements6 \ 51 | test-control-flow \ 52 | test-control-flow2 \ 53 | test-functions \ 54 | test-functions2 \ 55 | test-functions3 \ 56 | test-functions4 \ 57 | 58 | 59 | $(foreach test, $(TESTS), $(eval $(call make_test,$(test)))) 60 | 61 | 62 | .PHONY: test-all 63 | test-all: 64 | @for test in $(TESTS); do \ 65 | make -s $$test; \ 66 | done 67 | -------------------------------------------------------------------------------- /chapter10/RuntimeError.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Token.h" 5 | 6 | class RuntimeError: public std::runtime_error { 7 | public: 8 | const Token& token; 9 | 10 | RuntimeError(const Token& token, std::string_view message) 11 | : std::runtime_error{message.data()}, token{token} 12 | {} 13 | }; -------------------------------------------------------------------------------- /chapter10/Token.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include // std::move 6 | #include "TokenType.h" 7 | 8 | class Token { 9 | public: 10 | const TokenType type; 11 | const std::string lexeme; 12 | const std::any literal; 13 | const int line; 14 | 15 | Token(TokenType type, std::string lexeme, std::any literal, 16 | int line) 17 | : type{type}, lexeme{std::move(lexeme)}, 18 | literal{std::move(literal)}, line{line} 19 | {} 20 | 21 | std::string toString() const { 22 | std::string literal_text; 23 | 24 | switch (type) { 25 | case (IDENTIFIER): 26 | literal_text = lexeme; 27 | break; 28 | case (STRING): 29 | literal_text = std::any_cast(literal); 30 | break; 31 | case (NUMBER): 32 | literal_text = std::to_string(std::any_cast(literal)); 33 | break; 34 | case (TRUE): 35 | literal_text = "true"; 36 | break; 37 | case (FALSE): 38 | literal_text = "false"; 39 | break; 40 | default: 41 | literal_text = "nil"; 42 | } 43 | 44 | return ::toString(type) + " " + lexeme + " " + literal_text; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /chapter10/TokenType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | enum TokenType { 6 | // Single-character tokens. 7 | LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, 8 | COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, 9 | 10 | // One or two character tokens. 11 | BANG, BANG_EQUAL, 12 | EQUAL, EQUAL_EQUAL, 13 | GREATER, GREATER_EQUAL, 14 | LESS, LESS_EQUAL, 15 | 16 | // Literals. 17 | IDENTIFIER, STRING, NUMBER, 18 | 19 | // Keywords. 20 | AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR, 21 | PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE, 22 | 23 | END_OF_FILE 24 | }; 25 | 26 | std::string toString(TokenType type) { 27 | static const std::string strings[] = { 28 | "LEFT_PAREN", "RIGHT_PAREN", "LEFT_BRACE", "RIGHT_BRACE", 29 | "COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON", "SLASH", "STAR", 30 | "BANG", "BANG_EQUAL", 31 | "EQUAL", "EQUAL_EQUAL", 32 | "GREATER", "GREATER_EQUAL", 33 | "LESS", "LESS_EQUAL", 34 | "IDENTIFIER", "STRING", "NUMBER", 35 | "AND", "CLASS", "ELSE", "FALSE", "FUN", "FOR", "IF", "NIL", "OR", 36 | "PRINT", "RETURN", "SUPER", "THIS", "TRUE", "VAR", "WHILE", 37 | "END_OF_FILE" 38 | }; 39 | 40 | return strings[static_cast(type)]; 41 | } 42 | -------------------------------------------------------------------------------- /chapter10/tests/test-control-flow.lox: -------------------------------------------------------------------------------- 1 | print "hi" or 2; // "hi". 2 | print nil or "yes"; // "yes". 3 | -------------------------------------------------------------------------------- /chapter10/tests/test-control-flow.lox.expected: -------------------------------------------------------------------------------- 1 | hi 2 | yes 3 | -------------------------------------------------------------------------------- /chapter10/tests/test-control-flow2.lox: -------------------------------------------------------------------------------- 1 | var a = 0; 2 | var temp; 3 | 4 | for (var b = 1; a < 10000; b = temp + b) { 5 | print a; 6 | temp = a; 7 | a = b; 8 | } 9 | -------------------------------------------------------------------------------- /chapter10/tests/test-control-flow2.lox.expected: -------------------------------------------------------------------------------- 1 | 0.000000 2 | 1.000000 3 | 1.000000 4 | 2.000000 5 | 3.000000 6 | 5.000000 7 | 8.000000 8 | 13.000000 9 | 21.000000 10 | 34.000000 11 | 55.000000 12 | 89.000000 13 | 144.000000 14 | 233.000000 15 | 377.000000 16 | 610.000000 17 | 987.000000 18 | 1597.000000 19 | 2584.000000 20 | 4181.000000 21 | 6765.000000 22 | -------------------------------------------------------------------------------- /chapter10/tests/test-functions.lox: -------------------------------------------------------------------------------- 1 | fun add(a, b) { 2 | print a + b; 3 | } 4 | 5 | print add; // "". 6 | -------------------------------------------------------------------------------- /chapter10/tests/test-functions.lox.expected: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /chapter10/tests/test-functions2.lox: -------------------------------------------------------------------------------- 1 | fun sayHi(first, last) { 2 | print "Hi, " + first + " " + last + "!"; 3 | } 4 | 5 | sayHi("Dear", "Reader"); 6 | -------------------------------------------------------------------------------- /chapter10/tests/test-functions2.lox.expected: -------------------------------------------------------------------------------- 1 | Hi, Dear Reader! 2 | -------------------------------------------------------------------------------- /chapter10/tests/test-functions3.lox: -------------------------------------------------------------------------------- 1 | fun fib(n) { 2 | if (n <= 1) return n; 3 | return fib(n - 2) + fib(n - 1); 4 | } 5 | 6 | for (var i = 0; i < 20; i = i + 1) { 7 | print fib(i); 8 | } 9 | -------------------------------------------------------------------------------- /chapter10/tests/test-functions3.lox.expected: -------------------------------------------------------------------------------- 1 | 0.000000 2 | 1.000000 3 | 1.000000 4 | 2.000000 5 | 3.000000 6 | 5.000000 7 | 8.000000 8 | 13.000000 9 | 21.000000 10 | 34.000000 11 | 55.000000 12 | 89.000000 13 | 144.000000 14 | 233.000000 15 | 377.000000 16 | 610.000000 17 | 987.000000 18 | 1597.000000 19 | 2584.000000 20 | 4181.000000 21 | -------------------------------------------------------------------------------- /chapter10/tests/test-functions4.lox: -------------------------------------------------------------------------------- 1 | fun makeCounter() { 2 | var i = 0; 3 | fun count() { 4 | i = i + 1; 5 | print i; 6 | } 7 | 8 | return count; 9 | } 10 | 11 | var counter = makeCounter(); 12 | counter(); // "1". 13 | counter(); // "2". 14 | -------------------------------------------------------------------------------- /chapter10/tests/test-functions4.lox.expected: -------------------------------------------------------------------------------- 1 | 1.000000 2 | 2.000000 3 | -------------------------------------------------------------------------------- /chapter10/tests/test-statements.lox: -------------------------------------------------------------------------------- 1 | print "one"; 2 | print true; 3 | print 2 + 1; 4 | -------------------------------------------------------------------------------- /chapter10/tests/test-statements.lox.expected: -------------------------------------------------------------------------------- 1 | one 2 | true 3 | 3.000000 4 | -------------------------------------------------------------------------------- /chapter10/tests/test-statements2.lox: -------------------------------------------------------------------------------- 1 | var a = "before"; 2 | print a; // "before". 3 | var a = "after"; 4 | print a; // "after". 5 | -------------------------------------------------------------------------------- /chapter10/tests/test-statements2.lox.expected: -------------------------------------------------------------------------------- 1 | before 2 | after 3 | -------------------------------------------------------------------------------- /chapter10/tests/test-statements3.lox: -------------------------------------------------------------------------------- 1 | var a; 2 | print a; // "nil". 3 | -------------------------------------------------------------------------------- /chapter10/tests/test-statements3.lox.expected: -------------------------------------------------------------------------------- 1 | nil 2 | -------------------------------------------------------------------------------- /chapter10/tests/test-statements4.lox: -------------------------------------------------------------------------------- 1 | var a = 1; 2 | var b = 2; 3 | print a + b; 4 | -------------------------------------------------------------------------------- /chapter10/tests/test-statements4.lox.expected: -------------------------------------------------------------------------------- 1 | 3.000000 2 | -------------------------------------------------------------------------------- /chapter10/tests/test-statements5.lox: -------------------------------------------------------------------------------- 1 | var a = 1; 2 | print a = 2; // "2". 3 | -------------------------------------------------------------------------------- /chapter10/tests/test-statements5.lox.expected: -------------------------------------------------------------------------------- 1 | 2.000000 2 | -------------------------------------------------------------------------------- /chapter10/tests/test-statements6.lox: -------------------------------------------------------------------------------- 1 | var a = "global a"; 2 | var b = "global b"; 3 | var c = "global c"; 4 | { 5 | var a = "outer a"; 6 | var b = "outer b"; 7 | { 8 | var a = "inner a"; 9 | print a; 10 | print b; 11 | print c; 12 | } 13 | print a; 14 | print b; 15 | print c; 16 | } 17 | print a; 18 | print b; 19 | print c; 20 | -------------------------------------------------------------------------------- /chapter10/tests/test-statements6.lox.expected: -------------------------------------------------------------------------------- 1 | inner a 2 | outer b 3 | global c 4 | outer a 5 | outer b 6 | global c 7 | global a 8 | global b 9 | global c 10 | -------------------------------------------------------------------------------- /chapter11/AstPrinter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "Expr.h" 11 | 12 | class AstPrinter: public ExprVisitor { 13 | public: 14 | std::string print(std::shared_ptr expr) { 15 | return std::any_cast(expr->accept(*this)); 16 | } 17 | 18 | std::any visitBinaryExpr(std::shared_ptr expr) override { 19 | return parenthesize(expr->op.lexeme, 20 | expr->left, expr->right); 21 | } 22 | 23 | std::any visitGroupingExpr( 24 | std::shared_ptr expr) override { 25 | return parenthesize("group", expr->expression); 26 | } 27 | 28 | std::any visitLiteralExpr(std::shared_ptr expr) override { 29 | auto& value_type = expr->value.type(); 30 | 31 | if (value_type == typeid(nullptr)) { 32 | return "nil"; 33 | } else if (value_type == typeid(std::string)) { 34 | return std::any_cast(expr->value); 35 | } else if (value_type == typeid(double)) { 36 | return std::to_string(std::any_cast(expr->value)); 37 | } else if (value_type == typeid(bool)) { 38 | return std::any_cast(expr->value) ? "true" : "false"; 39 | } 40 | 41 | return "Error in visitLiteralExpr: literal type not recognized."; 42 | } 43 | 44 | std::any visitUnaryExpr(std::shared_ptr expr) override { 45 | return parenthesize(expr->op.lexeme, expr->right); 46 | } 47 | 48 | private: 49 | template 50 | std::string parenthesize(std::string_view name, E... expr) 51 | { 52 | assert((... && std::is_same_v>)); 53 | 54 | std::ostringstream builder; 55 | 56 | builder << "(" << name; 57 | ((builder << " " << print(expr)), ...); 58 | builder << ")"; 59 | 60 | return builder.str(); 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /chapter11/AstPrinterDriver.cpp: -------------------------------------------------------------------------------- 1 | #include "AstPrinter.h" 2 | 3 | int main(int argc, char* argv[]) { 4 | std::shared_ptr expression = std::make_shared( 5 | std::make_shared( 6 | Token{MINUS, "-", nullptr, 1}, 7 | std::make_shared(123.) 8 | ), 9 | Token{STAR, "*", nullptr, 1}, 10 | std::make_shared( 11 | std::make_shared(45.67))); 12 | 13 | std::cout << AstPrinter{}.print(expression) << "\n"; 14 | } -------------------------------------------------------------------------------- /chapter11/Environment.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include // less 5 | #include 6 | #include 7 | #include 8 | #include // std::move 9 | #include "Error.h" 10 | #include "Token.h" 11 | 12 | class Environment: public std::enable_shared_from_this { 13 | friend class Interpreter; 14 | 15 | std::shared_ptr enclosing; 16 | std::map values; 17 | 18 | public: 19 | Environment() 20 | : enclosing{nullptr} 21 | {} 22 | 23 | Environment(std::shared_ptr enclosing) 24 | : enclosing{std::move(enclosing)} 25 | {} 26 | 27 | std::any get(const Token& name) { 28 | auto elem = values.find(name.lexeme); 29 | if (elem != values.end()) { 30 | return elem->second; 31 | } 32 | 33 | if (enclosing != nullptr) return enclosing->get(name); 34 | 35 | throw RuntimeError(name, 36 | "Undefined variable '" + name.lexeme + "'."); 37 | } 38 | 39 | void assign(const Token& name, std::any value) { 40 | auto elem = values.find(name.lexeme); 41 | if (elem != values.end()) { 42 | elem->second = std::move(value); 43 | return; 44 | } 45 | 46 | if (enclosing != nullptr) { 47 | enclosing->assign(name, std::move(value)); 48 | return; 49 | } 50 | 51 | throw RuntimeError(name, 52 | "Undefined variable '" + name.lexeme + "'."); 53 | } 54 | 55 | void define(const std::string& name, std::any value) { 56 | values[name] = std::move(value); 57 | } 58 | 59 | std::shared_ptr ancestor(int distance) { 60 | std::shared_ptr environment = shared_from_this(); 61 | for (int i = 0; i < distance; ++i) { 62 | environment = environment->enclosing; 63 | } 64 | 65 | return environment; 66 | } 67 | 68 | std::any getAt(int distance, const std::string& name) { 69 | return ancestor(distance)->values[name]; 70 | } 71 | 72 | void assignAt(int distance, const Token& name, std::any value) { 73 | ancestor(distance)->values[name.lexeme] = std::move(value); 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /chapter11/Error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "RuntimeError.h" 6 | #include "Token.h" 7 | 8 | inline bool hadError = false; 9 | inline bool hadRuntimeError = false; 10 | 11 | static void report(int line, std::string_view where, 12 | std::string_view message) { 13 | std::cerr << 14 | "[line " << line << "] Error" << where << ": " << message << 15 | "\n"; 16 | hadError = true; 17 | } 18 | 19 | void error(const Token& token, std::string_view message) { 20 | if (token.type == END_OF_FILE) { 21 | report(token.line, " at end", message); 22 | } else { 23 | report(token.line, " at '" + token.lexeme + "'", message); 24 | } 25 | } 26 | 27 | void error(int line, std::string_view message) { 28 | report(line, "", message); 29 | } 30 | 31 | void runtimeError(const RuntimeError& error) { 32 | std::cerr << error.what() << 33 | "\n[line " << error.token.line << "]\n"; 34 | hadRuntimeError = true; 35 | } 36 | -------------------------------------------------------------------------------- /chapter11/Lox.cpp: -------------------------------------------------------------------------------- 1 | #include // std::strerror 2 | #include // readFile 3 | #include // std::getline 4 | #include 5 | #include 6 | #include "Error.h" 7 | #include "Interpreter.h" 8 | #include "Parser.h" 9 | #include "Resolver.h" 10 | #include "Scanner.h" 11 | 12 | // It's not good practice to include .cpp files, but in our case it 13 | // allows us to lay out the files similarly to the Java code while 14 | // avoiding circular dependencies. 15 | #include "LoxFunction.cpp" // Chapter 10 - Functions 16 | 17 | std::string readFile(std::string_view path) { 18 | std::ifstream file{path.data(), std::ios::in | std::ios::binary | 19 | std::ios::ate}; 20 | if (!file) { 21 | std::cerr << "Failed to open file " << path << ": " 22 | << std::strerror(errno) << "\n"; 23 | std::exit(74); 24 | }; 25 | 26 | std::string contents; 27 | contents.resize(file.tellg()); 28 | 29 | file.seekg(0, std::ios::beg); 30 | file.read(contents.data(), contents.size()); 31 | 32 | return contents; 33 | } 34 | 35 | Interpreter interpreter{}; 36 | 37 | void run(std::string_view source) { 38 | Scanner scanner {source}; 39 | std::vector tokens = scanner.scanTokens(); 40 | Parser parser{tokens}; 41 | std::vector> statements = parser.parse(); 42 | 43 | // Stop if there was a syntax error. 44 | if (hadError) return; 45 | 46 | Resolver resolver{interpreter}; 47 | resolver.resolve(statements); 48 | 49 | // Stop if there was a resolution error. 50 | if (hadError) return; 51 | 52 | interpreter.interpret(statements); 53 | } 54 | 55 | void runFile(std::string_view path) { 56 | std::string contents = readFile(path); 57 | run(contents); 58 | 59 | // Indicate an error in the exit code. 60 | if (hadError) std::exit(65); 61 | if (hadRuntimeError) std::exit(70); 62 | } 63 | 64 | void runPrompt() { 65 | for (;;) { 66 | std::cout << "> "; 67 | std::string line; 68 | if (!std::getline(std::cin, line)) break; 69 | run(line); 70 | hadError = false; 71 | } 72 | } 73 | 74 | int main(int argc, char* argv[]) { 75 | if (argc > 2) { 76 | std::cout << "Usage: jlox [script]\n"; 77 | std::exit(64); 78 | } else if (argc == 2) { 79 | runFile(argv[1]); 80 | } else { 81 | runPrompt(); 82 | } 83 | } -------------------------------------------------------------------------------- /chapter11/LoxCallable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class Interpreter; 8 | 9 | class LoxCallable { 10 | public: 11 | virtual int arity() = 0; 12 | virtual std::any call(Interpreter& interpreter, 13 | std::vector arguments) = 0; 14 | virtual std::string toString() = 0; 15 | virtual ~LoxCallable() = default; 16 | }; -------------------------------------------------------------------------------- /chapter11/LoxFunction.cpp: -------------------------------------------------------------------------------- 1 | #include "LoxFunction.h" 2 | #include // std::move 3 | #include "Environment.h" 4 | #include "Interpreter.h" 5 | #include "Stmt.h" 6 | 7 | LoxFunction::LoxFunction(std::shared_ptr declaration, 8 | std::shared_ptr closure) 9 | : closure{std::move(closure)}, declaration{std::move(declaration)} 10 | {} 11 | 12 | std::string LoxFunction::toString() { 13 | return "name.lexeme + ">"; 14 | } 15 | 16 | int LoxFunction::arity() { 17 | return declaration->params.size(); 18 | } 19 | 20 | std::any LoxFunction::call(Interpreter& interpreter, 21 | std::vector arguments) { 22 | auto environment = std::make_shared(closure); 23 | for (int i = 0; i < declaration->params.size(); ++i) { 24 | environment->define(declaration->params[i].lexeme, 25 | arguments[i]); 26 | } 27 | 28 | try { 29 | interpreter.executeBlock(declaration->body, environment); 30 | } catch (LoxReturn returnValue) { 31 | return returnValue.value; 32 | } 33 | 34 | return nullptr; 35 | } 36 | -------------------------------------------------------------------------------- /chapter11/LoxFunction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "LoxCallable.h" 8 | 9 | class Environment; 10 | class Function; 11 | class LoxInstance; 12 | 13 | class LoxFunction: public LoxCallable { 14 | std::shared_ptr declaration; 15 | std::shared_ptr closure; 16 | 17 | public: 18 | LoxFunction(std::shared_ptr declaration, 19 | std::shared_ptr closure); 20 | std::string toString() override; 21 | int arity() override; 22 | std::any call(Interpreter& interpreter, 23 | std::vector arguments) override; 24 | }; 25 | -------------------------------------------------------------------------------- /chapter11/LoxReturn.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct LoxReturn { 6 | const std::any value; 7 | }; -------------------------------------------------------------------------------- /chapter11/Makefile: -------------------------------------------------------------------------------- 1 | CXX := g++ 2 | CXXFLAGS := -ggdb -std=c++17 3 | CPPFLAGS := -MMD 4 | 5 | COMPILE := $(CXX) $(CXXFLAGS) $(CPPFLAGS) 6 | 7 | SRCS := AstPrinterDriver.cpp GenerateAst.cpp Lox.cpp 8 | DEPS := $(SRCS:.cpp=.d) 9 | 10 | 11 | jlox: Expr.h Stmt.h Lox.o 12 | @$(COMPILE) Lox.o -o $@ 13 | 14 | 15 | ast_printer: Expr.h Stmt.h AstPrinterDriver.o 16 | @$(COMPILE) AstPrinterDriver.o -o $@ 17 | 18 | 19 | generate_ast: GenerateAst.o 20 | @$(COMPILE) $< -o $@ 21 | 22 | 23 | Expr.h Stmt.h: generate_ast 24 | @./generate_ast . 25 | 26 | 27 | .PHONY: clean 28 | clean: 29 | rm -f *.d *.o ast_printer generate_ast jlox 30 | 31 | 32 | -include $(DEPS) 33 | 34 | 35 | define make_test 36 | .PHONY: $(1) 37 | $(1): 38 | @make jlox >/dev/null 39 | @echo "testing jlox with $(1).lox ..." 40 | @./jlox tests/$(1).lox | diff -u --color tests/$(1).lox.expected -; 41 | endef 42 | 43 | 44 | define make_test_error 45 | .PHONY: $(1) 46 | $(1): 47 | @make jlox >/dev/null 48 | @echo "testing jlox with $(1).lox ..." 49 | @./jlox tests/$(1).lox 2>&1 | diff -u --color tests/$(1).lox.expected -; 50 | endef 51 | 52 | 53 | TESTS = \ 54 | test-statements \ 55 | test-statements2 \ 56 | test-statements3 \ 57 | test-statements4 \ 58 | test-statements5 \ 59 | test-statements6 \ 60 | test-control-flow \ 61 | test-control-flow2 \ 62 | test-functions \ 63 | test-functions2 \ 64 | test-functions3 \ 65 | test-functions4 \ 66 | test-resolving \ 67 | 68 | 69 | TEST_ERRORS = \ 70 | test-resolving2 \ 71 | test-resolving3 \ 72 | test-resolving4 \ 73 | 74 | 75 | $(foreach test, $(TESTS), $(eval $(call make_test,$(test)))) 76 | $(foreach test, $(TEST_ERRORS), $(eval $(call make_test_error,$(test)))) 77 | 78 | 79 | .PHONY: test-all 80 | test-all: 81 | @for test in $(TESTS) $(TEST_ERRORS); do \ 82 | make -s $$test; \ 83 | done 84 | -------------------------------------------------------------------------------- /chapter11/RuntimeError.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Token.h" 5 | 6 | class RuntimeError: public std::runtime_error { 7 | public: 8 | const Token& token; 9 | 10 | RuntimeError(const Token& token, std::string_view message) 11 | : std::runtime_error{message.data()}, token{token} 12 | {} 13 | }; -------------------------------------------------------------------------------- /chapter11/Token.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include // std::move 6 | #include "TokenType.h" 7 | 8 | class Token { 9 | public: 10 | const TokenType type; 11 | const std::string lexeme; 12 | const std::any literal; 13 | const int line; 14 | 15 | Token(TokenType type, std::string lexeme, std::any literal, 16 | int line) 17 | : type{type}, lexeme{std::move(lexeme)}, 18 | literal{std::move(literal)}, line{line} 19 | {} 20 | 21 | std::string toString() const { 22 | std::string literal_text; 23 | 24 | switch (type) { 25 | case (IDENTIFIER): 26 | literal_text = lexeme; 27 | break; 28 | case (STRING): 29 | literal_text = std::any_cast(literal); 30 | break; 31 | case (NUMBER): 32 | literal_text = std::to_string(std::any_cast(literal)); 33 | break; 34 | case (TRUE): 35 | literal_text = "true"; 36 | break; 37 | case (FALSE): 38 | literal_text = "false"; 39 | break; 40 | default: 41 | literal_text = "nil"; 42 | } 43 | 44 | return ::toString(type) + " " + lexeme + " " + literal_text; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /chapter11/TokenType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | enum TokenType { 6 | // Single-character tokens. 7 | LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, 8 | COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, 9 | 10 | // One or two character tokens. 11 | BANG, BANG_EQUAL, 12 | EQUAL, EQUAL_EQUAL, 13 | GREATER, GREATER_EQUAL, 14 | LESS, LESS_EQUAL, 15 | 16 | // Literals. 17 | IDENTIFIER, STRING, NUMBER, 18 | 19 | // Keywords. 20 | AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR, 21 | PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE, 22 | 23 | END_OF_FILE 24 | }; 25 | 26 | std::string toString(TokenType type) { 27 | static const std::string strings[] = { 28 | "LEFT_PAREN", "RIGHT_PAREN", "LEFT_BRACE", "RIGHT_BRACE", 29 | "COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON", "SLASH", "STAR", 30 | "BANG", "BANG_EQUAL", 31 | "EQUAL", "EQUAL_EQUAL", 32 | "GREATER", "GREATER_EQUAL", 33 | "LESS", "LESS_EQUAL", 34 | "IDENTIFIER", "STRING", "NUMBER", 35 | "AND", "CLASS", "ELSE", "FALSE", "FUN", "FOR", "IF", "NIL", "OR", 36 | "PRINT", "RETURN", "SUPER", "THIS", "TRUE", "VAR", "WHILE", 37 | "END_OF_FILE" 38 | }; 39 | 40 | return strings[static_cast(type)]; 41 | } 42 | -------------------------------------------------------------------------------- /chapter11/tests/test-control-flow.lox: -------------------------------------------------------------------------------- 1 | print "hi" or 2; // "hi". 2 | print nil or "yes"; // "yes". 3 | -------------------------------------------------------------------------------- /chapter11/tests/test-control-flow.lox.expected: -------------------------------------------------------------------------------- 1 | hi 2 | yes 3 | -------------------------------------------------------------------------------- /chapter11/tests/test-control-flow2.lox: -------------------------------------------------------------------------------- 1 | var a = 0; 2 | var temp; 3 | 4 | for (var b = 1; a < 10000; b = temp + b) { 5 | print a; 6 | temp = a; 7 | a = b; 8 | } 9 | -------------------------------------------------------------------------------- /chapter11/tests/test-control-flow2.lox.expected: -------------------------------------------------------------------------------- 1 | 0.000000 2 | 1.000000 3 | 1.000000 4 | 2.000000 5 | 3.000000 6 | 5.000000 7 | 8.000000 8 | 13.000000 9 | 21.000000 10 | 34.000000 11 | 55.000000 12 | 89.000000 13 | 144.000000 14 | 233.000000 15 | 377.000000 16 | 610.000000 17 | 987.000000 18 | 1597.000000 19 | 2584.000000 20 | 4181.000000 21 | 6765.000000 22 | -------------------------------------------------------------------------------- /chapter11/tests/test-functions.lox: -------------------------------------------------------------------------------- 1 | fun add(a, b) { 2 | print a + b; 3 | } 4 | 5 | print add; // "". 6 | -------------------------------------------------------------------------------- /chapter11/tests/test-functions.lox.expected: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /chapter11/tests/test-functions2.lox: -------------------------------------------------------------------------------- 1 | fun sayHi(first, last) { 2 | print "Hi, " + first + " " + last + "!"; 3 | } 4 | 5 | sayHi("Dear", "Reader"); 6 | -------------------------------------------------------------------------------- /chapter11/tests/test-functions2.lox.expected: -------------------------------------------------------------------------------- 1 | Hi, Dear Reader! 2 | -------------------------------------------------------------------------------- /chapter11/tests/test-functions3.lox: -------------------------------------------------------------------------------- 1 | fun fib(n) { 2 | if (n <= 1) return n; 3 | return fib(n - 2) + fib(n - 1); 4 | } 5 | 6 | for (var i = 0; i < 20; i = i + 1) { 7 | print fib(i); 8 | } 9 | -------------------------------------------------------------------------------- /chapter11/tests/test-functions3.lox.expected: -------------------------------------------------------------------------------- 1 | 0.000000 2 | 1.000000 3 | 1.000000 4 | 2.000000 5 | 3.000000 6 | 5.000000 7 | 8.000000 8 | 13.000000 9 | 21.000000 10 | 34.000000 11 | 55.000000 12 | 89.000000 13 | 144.000000 14 | 233.000000 15 | 377.000000 16 | 610.000000 17 | 987.000000 18 | 1597.000000 19 | 2584.000000 20 | 4181.000000 21 | -------------------------------------------------------------------------------- /chapter11/tests/test-functions4.lox: -------------------------------------------------------------------------------- 1 | fun makeCounter() { 2 | var i = 0; 3 | fun count() { 4 | i = i + 1; 5 | print i; 6 | } 7 | 8 | return count; 9 | } 10 | 11 | var counter = makeCounter(); 12 | counter(); // "1". 13 | counter(); // "2". 14 | -------------------------------------------------------------------------------- /chapter11/tests/test-functions4.lox.expected: -------------------------------------------------------------------------------- 1 | 1.000000 2 | 2.000000 3 | -------------------------------------------------------------------------------- /chapter11/tests/test-resolving.lox: -------------------------------------------------------------------------------- 1 | var a = "global"; 2 | { 3 | fun showA() { 4 | print a; 5 | } 6 | 7 | showA(); 8 | var a = "block"; 9 | showA(); 10 | } 11 | -------------------------------------------------------------------------------- /chapter11/tests/test-resolving.lox.expected: -------------------------------------------------------------------------------- 1 | global 2 | global 3 | -------------------------------------------------------------------------------- /chapter11/tests/test-resolving2.lox: -------------------------------------------------------------------------------- 1 | var a = "outer"; 2 | { 3 | var a = a; 4 | } 5 | -------------------------------------------------------------------------------- /chapter11/tests/test-resolving2.lox.expected: -------------------------------------------------------------------------------- 1 | [line 3] Error at 'a': Can't read local variable in its own initializer. 2 | -------------------------------------------------------------------------------- /chapter11/tests/test-resolving3.lox: -------------------------------------------------------------------------------- 1 | fun bad() { 2 | var a = "first"; 3 | var a = "second"; 4 | } 5 | -------------------------------------------------------------------------------- /chapter11/tests/test-resolving3.lox.expected: -------------------------------------------------------------------------------- 1 | [line 3] Error at 'a': Already a variable with this name in this scope. 2 | -------------------------------------------------------------------------------- /chapter11/tests/test-resolving4.lox: -------------------------------------------------------------------------------- 1 | return "at top level"; 2 | -------------------------------------------------------------------------------- /chapter11/tests/test-resolving4.lox.expected: -------------------------------------------------------------------------------- 1 | [line 1] Error at 'return': Can't return from top-level code. 2 | -------------------------------------------------------------------------------- /chapter11/tests/test-statements.lox: -------------------------------------------------------------------------------- 1 | print "one"; 2 | print true; 3 | print 2 + 1; 4 | -------------------------------------------------------------------------------- /chapter11/tests/test-statements.lox.expected: -------------------------------------------------------------------------------- 1 | one 2 | true 3 | 3.000000 4 | -------------------------------------------------------------------------------- /chapter11/tests/test-statements2.lox: -------------------------------------------------------------------------------- 1 | var a = "before"; 2 | print a; // "before". 3 | var a = "after"; 4 | print a; // "after". 5 | -------------------------------------------------------------------------------- /chapter11/tests/test-statements2.lox.expected: -------------------------------------------------------------------------------- 1 | before 2 | after 3 | -------------------------------------------------------------------------------- /chapter11/tests/test-statements3.lox: -------------------------------------------------------------------------------- 1 | var a; 2 | print a; // "nil". 3 | -------------------------------------------------------------------------------- /chapter11/tests/test-statements3.lox.expected: -------------------------------------------------------------------------------- 1 | nil 2 | -------------------------------------------------------------------------------- /chapter11/tests/test-statements4.lox: -------------------------------------------------------------------------------- 1 | var a = 1; 2 | var b = 2; 3 | print a + b; 4 | -------------------------------------------------------------------------------- /chapter11/tests/test-statements4.lox.expected: -------------------------------------------------------------------------------- 1 | 3.000000 2 | -------------------------------------------------------------------------------- /chapter11/tests/test-statements5.lox: -------------------------------------------------------------------------------- 1 | var a = 1; 2 | print a = 2; // "2". 3 | -------------------------------------------------------------------------------- /chapter11/tests/test-statements5.lox.expected: -------------------------------------------------------------------------------- 1 | 2.000000 2 | -------------------------------------------------------------------------------- /chapter11/tests/test-statements6.lox: -------------------------------------------------------------------------------- 1 | var a = "global a"; 2 | var b = "global b"; 3 | var c = "global c"; 4 | { 5 | var a = "outer a"; 6 | var b = "outer b"; 7 | { 8 | var a = "inner a"; 9 | print a; 10 | print b; 11 | print c; 12 | } 13 | print a; 14 | print b; 15 | print c; 16 | } 17 | print a; 18 | print b; 19 | print c; 20 | -------------------------------------------------------------------------------- /chapter11/tests/test-statements6.lox.expected: -------------------------------------------------------------------------------- 1 | inner a 2 | outer b 3 | global c 4 | outer a 5 | outer b 6 | global c 7 | global a 8 | global b 9 | global c 10 | -------------------------------------------------------------------------------- /chapter12/AstPrinter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "Expr.h" 11 | 12 | class AstPrinter: public ExprVisitor { 13 | public: 14 | std::string print(std::shared_ptr expr) { 15 | return std::any_cast(expr->accept(*this)); 16 | } 17 | 18 | std::any visitBinaryExpr(std::shared_ptr expr) override { 19 | return parenthesize(expr->op.lexeme, 20 | expr->left, expr->right); 21 | } 22 | 23 | std::any visitGroupingExpr( 24 | std::shared_ptr expr) override { 25 | return parenthesize("group", expr->expression); 26 | } 27 | 28 | std::any visitLiteralExpr(std::shared_ptr expr) override { 29 | auto& value_type = expr->value.type(); 30 | 31 | if (value_type == typeid(nullptr)) { 32 | return "nil"; 33 | } else if (value_type == typeid(std::string)) { 34 | return std::any_cast(expr->value); 35 | } else if (value_type == typeid(double)) { 36 | return std::to_string(std::any_cast(expr->value)); 37 | } else if (value_type == typeid(bool)) { 38 | return std::any_cast(expr->value) ? "true" : "false"; 39 | } 40 | 41 | return "Error in visitLiteralExpr: literal type not recognized."; 42 | } 43 | 44 | std::any visitUnaryExpr(std::shared_ptr expr) override { 45 | return parenthesize(expr->op.lexeme, expr->right); 46 | } 47 | 48 | private: 49 | template 50 | std::string parenthesize(std::string_view name, E... expr) 51 | { 52 | assert((... && std::is_same_v>)); 53 | 54 | std::ostringstream builder; 55 | 56 | builder << "(" << name; 57 | ((builder << " " << print(expr)), ...); 58 | builder << ")"; 59 | 60 | return builder.str(); 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /chapter12/AstPrinterDriver.cpp: -------------------------------------------------------------------------------- 1 | #include "AstPrinter.h" 2 | 3 | int main(int argc, char* argv[]) { 4 | std::shared_ptr expression = std::make_shared( 5 | std::make_shared( 6 | Token{MINUS, "-", nullptr, 1}, 7 | std::make_shared(123.) 8 | ), 9 | Token{STAR, "*", nullptr, 1}, 10 | std::make_shared( 11 | std::make_shared(45.67))); 12 | 13 | std::cout << AstPrinter{}.print(expression) << "\n"; 14 | } -------------------------------------------------------------------------------- /chapter12/Environment.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include // less 5 | #include 6 | #include 7 | #include 8 | #include // std::move 9 | #include "Error.h" 10 | #include "Token.h" 11 | 12 | class Environment: public std::enable_shared_from_this { 13 | friend class Interpreter; 14 | 15 | std::shared_ptr enclosing; 16 | std::map values; 17 | 18 | public: 19 | Environment() 20 | : enclosing{nullptr} 21 | {} 22 | 23 | Environment(std::shared_ptr enclosing) 24 | : enclosing{std::move(enclosing)} 25 | {} 26 | 27 | std::any get(const Token& name) { 28 | auto elem = values.find(name.lexeme); 29 | if (elem != values.end()) { 30 | return elem->second; 31 | } 32 | 33 | if (enclosing != nullptr) return enclosing->get(name); 34 | 35 | throw RuntimeError(name, 36 | "Undefined variable '" + name.lexeme + "'."); 37 | } 38 | 39 | void assign(const Token& name, std::any value) { 40 | auto elem = values.find(name.lexeme); 41 | if (elem != values.end()) { 42 | elem->second = std::move(value); 43 | return; 44 | } 45 | 46 | if (enclosing != nullptr) { 47 | enclosing->assign(name, std::move(value)); 48 | return; 49 | } 50 | 51 | throw RuntimeError(name, 52 | "Undefined variable '" + name.lexeme + "'."); 53 | } 54 | 55 | void define(const std::string& name, std::any value) { 56 | values[name] = std::move(value); 57 | } 58 | 59 | std::shared_ptr ancestor(int distance) { 60 | std::shared_ptr environment = shared_from_this(); 61 | for (int i = 0; i < distance; ++i) { 62 | environment = environment->enclosing; 63 | } 64 | 65 | return environment; 66 | } 67 | 68 | std::any getAt(int distance, const std::string& name) { 69 | return ancestor(distance)->values[name]; 70 | } 71 | 72 | void assignAt(int distance, const Token& name, std::any value) { 73 | ancestor(distance)->values[name.lexeme] = std::move(value); 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /chapter12/Error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "RuntimeError.h" 6 | #include "Token.h" 7 | 8 | inline bool hadError = false; 9 | inline bool hadRuntimeError = false; 10 | 11 | static void report(int line, std::string_view where, 12 | std::string_view message) { 13 | std::cerr << 14 | "[line " << line << "] Error" << where << ": " << message << 15 | "\n"; 16 | hadError = true; 17 | } 18 | 19 | void error(const Token& token, std::string_view message) { 20 | if (token.type == END_OF_FILE) { 21 | report(token.line, " at end", message); 22 | } else { 23 | report(token.line, " at '" + token.lexeme + "'", message); 24 | } 25 | } 26 | 27 | void error(int line, std::string_view message) { 28 | report(line, "", message); 29 | } 30 | 31 | void runtimeError(const RuntimeError& error) { 32 | std::cerr << error.what() << 33 | "\n[line " << error.token.line << "]\n"; 34 | hadRuntimeError = true; 35 | } 36 | -------------------------------------------------------------------------------- /chapter12/Lox.cpp: -------------------------------------------------------------------------------- 1 | #include // std::strerror 2 | #include // readFile 3 | #include // std::getline 4 | #include 5 | #include 6 | #include "Error.h" 7 | #include "Interpreter.h" 8 | #include "Parser.h" 9 | #include "Resolver.h" 10 | #include "Scanner.h" 11 | 12 | // It's not good practice to include .cpp files, but in our case it 13 | // allows us to lay out the files similarly to the Java code while 14 | // avoiding circular dependencies. 15 | #include "LoxFunction.cpp" // Chapter 10 - Functions 16 | #include "LoxClass.cpp" // Chapter 12 - Classes 17 | #include "LoxInstance.cpp" // Chapter 12 - Classes 18 | 19 | std::string readFile(std::string_view path) { 20 | std::ifstream file{path.data(), std::ios::in | std::ios::binary | 21 | std::ios::ate}; 22 | if (!file) { 23 | std::cerr << "Failed to open file " << path << ": " 24 | << std::strerror(errno) << "\n"; 25 | std::exit(74); 26 | }; 27 | 28 | std::string contents; 29 | contents.resize(file.tellg()); 30 | 31 | file.seekg(0, std::ios::beg); 32 | file.read(contents.data(), contents.size()); 33 | 34 | return contents; 35 | } 36 | 37 | Interpreter interpreter{}; 38 | 39 | void run(std::string_view source) { 40 | Scanner scanner {source}; 41 | std::vector tokens = scanner.scanTokens(); 42 | Parser parser{tokens}; 43 | std::vector> statements = parser.parse(); 44 | 45 | // Stop if there was a syntax error. 46 | if (hadError) return; 47 | 48 | Resolver resolver{interpreter}; 49 | resolver.resolve(statements); 50 | 51 | // Stop if there was a resolution error. 52 | if (hadError) return; 53 | 54 | interpreter.interpret(statements); 55 | } 56 | 57 | void runFile(std::string_view path) { 58 | std::string contents = readFile(path); 59 | run(contents); 60 | 61 | // Indicate an error in the exit code. 62 | if (hadError) std::exit(65); 63 | if (hadRuntimeError) std::exit(70); 64 | } 65 | 66 | void runPrompt() { 67 | for (;;) { 68 | std::cout << "> "; 69 | std::string line; 70 | if (!std::getline(std::cin, line)) break; 71 | run(line); 72 | hadError = false; 73 | } 74 | } 75 | 76 | int main(int argc, char* argv[]) { 77 | if (argc > 2) { 78 | std::cout << "Usage: jlox [script]\n"; 79 | std::exit(64); 80 | } else if (argc == 2) { 81 | runFile(argv[1]); 82 | } else { 83 | runPrompt(); 84 | } 85 | } -------------------------------------------------------------------------------- /chapter12/LoxCallable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class Interpreter; 8 | 9 | class LoxCallable { 10 | public: 11 | virtual int arity() = 0; 12 | virtual std::any call(Interpreter& interpreter, 13 | std::vector arguments) = 0; 14 | virtual std::string toString() = 0; 15 | virtual ~LoxCallable() = default; 16 | }; -------------------------------------------------------------------------------- /chapter12/LoxClass.cpp: -------------------------------------------------------------------------------- 1 | #include "LoxClass.h" 2 | #include // std::move 3 | 4 | // LoxClass::LoxClass(std::string name) 5 | // : name{std::move(name)} 6 | // {} 7 | 8 | LoxClass::LoxClass(std::string name, 9 | std::map> methods) 10 | : name{std::move(name)}, methods{std::move(methods)} 11 | {} 12 | 13 | std::shared_ptr LoxClass::findMethod( 14 | const std::string& name) { 15 | auto elem = methods.find(name); 16 | if (elem != methods.end()) { 17 | return elem->second; 18 | } 19 | 20 | return nullptr; 21 | } 22 | 23 | std::string LoxClass::toString() { 24 | return name; 25 | } 26 | 27 | std::any LoxClass::call(Interpreter& interpreter, 28 | std::vector arguments) { 29 | auto instance = std::make_shared(shared_from_this()); 30 | std::shared_ptr initializer = findMethod("init"); 31 | if (initializer != nullptr) { 32 | initializer->bind(instance)->call(interpreter, 33 | std::move(arguments)); 34 | } 35 | 36 | return instance; 37 | } 38 | 39 | int LoxClass::arity() { 40 | // return 0; 41 | 42 | std::shared_ptr initializer = findMethod("init"); 43 | if (initializer == nullptr) return 0; 44 | return initializer->arity(); 45 | } 46 | -------------------------------------------------------------------------------- /chapter12/LoxClass.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "LoxCallable.h" 9 | 10 | class Interpreter; 11 | class LoxFunction; 12 | 13 | // class LoxClass: public std::enable_shared_from_this { 14 | class LoxClass: public LoxCallable, 15 | public std::enable_shared_from_this { 16 | friend class LoxInstance; 17 | const std::string name; 18 | std::map> methods; 19 | 20 | public: 21 | // LoxClass(std::string name); 22 | LoxClass(std::string name, 23 | std::map> methods); 24 | 25 | std::shared_ptr findMethod(const std::string& name); 26 | // std::string toString(); 27 | std::string toString() override; 28 | std::any call(Interpreter& interpreter, 29 | std::vector arguments) override; 30 | int arity() override; 31 | }; 32 | -------------------------------------------------------------------------------- /chapter12/LoxFunction.cpp: -------------------------------------------------------------------------------- 1 | #include "LoxFunction.h" 2 | #include // std::move 3 | #include "Environment.h" 4 | #include "LoxInstance.h" 5 | #include "Interpreter.h" 6 | #include "Stmt.h" 7 | 8 | LoxFunction::LoxFunction(std::shared_ptr declaration, 9 | std::shared_ptr closure, 10 | bool isInitializer) 11 | : isInitializer{isInitializer}, closure{std::move(closure)}, 12 | declaration{std::move(declaration)} 13 | {} 14 | 15 | std::shared_ptr LoxFunction::bind( 16 | std::shared_ptr instance) { 17 | auto environment = std::make_shared(closure); 18 | environment->define("this", instance); 19 | // return std::make_shared(declaration, environment); 20 | return std::make_shared(declaration, environment, 21 | isInitializer); 22 | } 23 | 24 | std::string LoxFunction::toString() { 25 | return "name.lexeme + ">"; 26 | } 27 | 28 | int LoxFunction::arity() { 29 | return declaration->params.size(); 30 | } 31 | 32 | std::any LoxFunction::call(Interpreter& interpreter, 33 | std::vector arguments) { 34 | auto environment = std::make_shared(closure); 35 | for (int i = 0; i < declaration->params.size(); ++i) { 36 | environment->define(declaration->params[i].lexeme, 37 | arguments[i]); 38 | } 39 | 40 | try { 41 | interpreter.executeBlock(declaration->body, environment); 42 | } catch (LoxReturn returnValue) { 43 | if (isInitializer) return closure->getAt(0, "this"); 44 | 45 | return returnValue.value; 46 | } 47 | 48 | if (isInitializer) return closure->getAt(0, "this"); 49 | return nullptr; 50 | } 51 | -------------------------------------------------------------------------------- /chapter12/LoxFunction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "LoxCallable.h" 8 | 9 | class Environment; 10 | class Function; 11 | class LoxInstance; 12 | 13 | class LoxFunction: public LoxCallable { 14 | std::shared_ptr declaration; 15 | std::shared_ptr closure; 16 | 17 | bool isInitializer; 18 | 19 | public: 20 | LoxFunction(std::shared_ptr declaration, 21 | std::shared_ptr closure, 22 | bool isInitializer); 23 | std::shared_ptr bind( 24 | std::shared_ptr instance); 25 | std::string toString() override; 26 | int arity() override; 27 | std::any call(Interpreter& interpreter, 28 | std::vector arguments) override; 29 | }; 30 | -------------------------------------------------------------------------------- /chapter12/LoxInstance.cpp: -------------------------------------------------------------------------------- 1 | #include "LoxInstance.h" 2 | #include // std::move 3 | // #include "Error.h" 4 | 5 | LoxInstance::LoxInstance(std::shared_ptr klass) 6 | : klass{std::move(klass)} 7 | {} 8 | 9 | std::any LoxInstance::get(const Token& name) { 10 | auto elem = fields.find(name.lexeme); 11 | if (elem != fields.end()) { 12 | return elem->second; 13 | } 14 | 15 | std::shared_ptr method = 16 | klass->findMethod(name.lexeme); 17 | // if (method != nullptr) return method; 18 | if (method != nullptr) return method->bind(shared_from_this()); 19 | 20 | throw RuntimeError(name, 21 | "Undefined property '" + name.lexeme + "'."); 22 | } 23 | 24 | void LoxInstance::set(const Token& name, std::any value) { 25 | fields[name.lexeme] = std::move(value); 26 | } 27 | 28 | std::string LoxInstance::toString() { 29 | return klass->name + " instance"; 30 | } 31 | -------------------------------------------------------------------------------- /chapter12/LoxInstance.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class LoxClass; 9 | class Token; 10 | 11 | class LoxInstance: public std::enable_shared_from_this { 12 | std::shared_ptr klass; 13 | std::map fields; 14 | 15 | public: 16 | LoxInstance(std::shared_ptr klass); 17 | std::any get(const Token& name); 18 | void set(const Token& name, std::any value); 19 | std::string toString(); 20 | }; 21 | -------------------------------------------------------------------------------- /chapter12/LoxReturn.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct LoxReturn { 6 | const std::any value; 7 | }; -------------------------------------------------------------------------------- /chapter12/Makefile: -------------------------------------------------------------------------------- 1 | CXX := g++ 2 | CXXFLAGS := -ggdb -std=c++17 3 | CPPFLAGS := -MMD 4 | 5 | COMPILE := $(CXX) $(CXXFLAGS) $(CPPFLAGS) 6 | 7 | SRCS := AstPrinterDriver.cpp GenerateAst.cpp Lox.cpp 8 | DEPS := $(SRCS:.cpp=.d) 9 | 10 | 11 | jlox: Expr.h Stmt.h Lox.o 12 | @$(COMPILE) Lox.o -o $@ 13 | 14 | 15 | ast_printer: Expr.h Stmt.h AstPrinterDriver.o 16 | @$(COMPILE) AstPrinterDriver.o -o $@ 17 | 18 | 19 | generate_ast: GenerateAst.o 20 | @$(COMPILE) $< -o $@ 21 | 22 | 23 | Expr.h Stmt.h: generate_ast 24 | @./generate_ast . 25 | 26 | 27 | .PHONY: clean 28 | clean: 29 | rm -f *.d *.o ast_printer generate_ast jlox 30 | 31 | 32 | -include $(DEPS) 33 | 34 | 35 | define make_test 36 | .PHONY: $(1) 37 | $(1): 38 | @make jlox >/dev/null 39 | @echo "testing jlox with $(1).lox ..." 40 | @./jlox tests/$(1).lox | diff -u --color tests/$(1).lox.expected -; 41 | endef 42 | 43 | 44 | define make_test_error 45 | .PHONY: $(1) 46 | $(1): 47 | @make jlox >/dev/null 48 | @echo "testing jlox with $(1).lox ..." 49 | @./jlox tests/$(1).lox 2>&1 | diff -u --color tests/$(1).lox.expected -; 50 | endef 51 | 52 | 53 | TESTS = \ 54 | test-statements \ 55 | test-statements2 \ 56 | test-statements3 \ 57 | test-statements4 \ 58 | test-statements5 \ 59 | test-statements6 \ 60 | test-control-flow \ 61 | test-control-flow2 \ 62 | test-functions \ 63 | test-functions2 \ 64 | test-functions3 \ 65 | test-functions4 \ 66 | test-resolving \ 67 | test-classes \ 68 | test-classes2 \ 69 | test-classes3 \ 70 | test-classes4 \ 71 | test-classes5 \ 72 | test-classes6 \ 73 | test-classes7 \ 74 | test-classes8 \ 75 | test-classes9 \ 76 | test-classes12 \ 77 | 78 | 79 | TEST_ERRORS = \ 80 | test-resolving2 \ 81 | test-resolving3 \ 82 | test-resolving4 \ 83 | test-classes10 \ 84 | test-classes11 \ 85 | test-classes13 \ 86 | 87 | 88 | $(foreach test, $(TESTS), $(eval $(call make_test,$(test)))) 89 | $(foreach test, $(TEST_ERRORS), $(eval $(call make_test_error,$(test)))) 90 | 91 | 92 | .PHONY: test-all 93 | test-all: 94 | @for test in $(TESTS) $(TEST_ERRORS); do \ 95 | make -s $$test; \ 96 | done 97 | -------------------------------------------------------------------------------- /chapter12/RuntimeError.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Token.h" 5 | 6 | class RuntimeError: public std::runtime_error { 7 | public: 8 | const Token& token; 9 | 10 | RuntimeError(const Token& token, std::string_view message) 11 | : std::runtime_error{message.data()}, token{token} 12 | {} 13 | }; -------------------------------------------------------------------------------- /chapter12/Token.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include // std::move 6 | #include "TokenType.h" 7 | 8 | class Token { 9 | public: 10 | const TokenType type; 11 | const std::string lexeme; 12 | const std::any literal; 13 | const int line; 14 | 15 | Token(TokenType type, std::string lexeme, std::any literal, 16 | int line) 17 | : type{type}, lexeme{std::move(lexeme)}, 18 | literal{std::move(literal)}, line{line} 19 | {} 20 | 21 | std::string toString() const { 22 | std::string literal_text; 23 | 24 | switch (type) { 25 | case (IDENTIFIER): 26 | literal_text = lexeme; 27 | break; 28 | case (STRING): 29 | literal_text = std::any_cast(literal); 30 | break; 31 | case (NUMBER): 32 | literal_text = std::to_string(std::any_cast(literal)); 33 | break; 34 | case (TRUE): 35 | literal_text = "true"; 36 | break; 37 | case (FALSE): 38 | literal_text = "false"; 39 | break; 40 | default: 41 | literal_text = "nil"; 42 | } 43 | 44 | return ::toString(type) + " " + lexeme + " " + literal_text; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /chapter12/TokenType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | enum TokenType { 6 | // Single-character tokens. 7 | LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, 8 | COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, 9 | 10 | // One or two character tokens. 11 | BANG, BANG_EQUAL, 12 | EQUAL, EQUAL_EQUAL, 13 | GREATER, GREATER_EQUAL, 14 | LESS, LESS_EQUAL, 15 | 16 | // Literals. 17 | IDENTIFIER, STRING, NUMBER, 18 | 19 | // Keywords. 20 | AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR, 21 | PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE, 22 | 23 | END_OF_FILE 24 | }; 25 | 26 | std::string toString(TokenType type) { 27 | static const std::string strings[] = { 28 | "LEFT_PAREN", "RIGHT_PAREN", "LEFT_BRACE", "RIGHT_BRACE", 29 | "COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON", "SLASH", "STAR", 30 | "BANG", "BANG_EQUAL", 31 | "EQUAL", "EQUAL_EQUAL", 32 | "GREATER", "GREATER_EQUAL", 33 | "LESS", "LESS_EQUAL", 34 | "IDENTIFIER", "STRING", "NUMBER", 35 | "AND", "CLASS", "ELSE", "FALSE", "FUN", "FOR", "IF", "NIL", "OR", 36 | "PRINT", "RETURN", "SUPER", "THIS", "TRUE", "VAR", "WHILE", 37 | "END_OF_FILE" 38 | }; 39 | 40 | return strings[static_cast(type)]; 41 | } 42 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes.lox: -------------------------------------------------------------------------------- 1 | class DevonshireCream { 2 | serveOn() { 3 | return "Scones"; 4 | } 5 | } 6 | 7 | print DevonshireCream; // Prints "DevonshireCream". 8 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes.lox.expected: -------------------------------------------------------------------------------- 1 | DevonshireCream 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes10.lox: -------------------------------------------------------------------------------- 1 | print this; 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes10.lox.expected: -------------------------------------------------------------------------------- 1 | [line 1] Error at 'this': Can't use 'this' outside of a class. 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes11.lox: -------------------------------------------------------------------------------- 1 | fun notAMethod() { 2 | print this; 3 | } 4 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes11.lox.expected: -------------------------------------------------------------------------------- 1 | [line 2] Error at 'this': Can't use 'this' outside of a class. 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes12.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | init() { 3 | print this; 4 | } 5 | } 6 | 7 | var foo = Foo(); 8 | print foo.init(); 9 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes12.lox.expected: -------------------------------------------------------------------------------- 1 | Foo instance 2 | Foo instance 3 | Foo instance 4 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes13.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | init() { 3 | return "something else"; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes13.lox.expected: -------------------------------------------------------------------------------- 1 | [line 3] Error at 'return': Can't return a value from an initializer. 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes2.lox: -------------------------------------------------------------------------------- 1 | class Bagel {} 2 | var bagel = Bagel(); 3 | print bagel; // Prints "Bagel instance". 4 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes2.lox.expected: -------------------------------------------------------------------------------- 1 | Bagel instance 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes3.lox: -------------------------------------------------------------------------------- 1 | class Box {} 2 | 3 | fun notMethod(argument) { 4 | print "called function with " + argument; 5 | } 6 | 7 | var box = Box(); 8 | box.function = notMethod; 9 | box.function("argument"); 10 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes3.lox.expected: -------------------------------------------------------------------------------- 1 | called function with argument 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes4.lox: -------------------------------------------------------------------------------- 1 | class Person { 2 | sayName() { 3 | print this.name; 4 | } 5 | } 6 | 7 | var jane = Person(); 8 | jane.name = "Jane"; 9 | 10 | var method = jane.sayName; 11 | method(); // ? 12 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes4.lox.expected: -------------------------------------------------------------------------------- 1 | Jane 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes5.lox: -------------------------------------------------------------------------------- 1 | class Person { 2 | sayName() { 3 | print this.name; 4 | } 5 | } 6 | 7 | var jane = Person(); 8 | jane.name = "Jane"; 9 | 10 | var bill = Person(); 11 | bill.name = "Bill"; 12 | 13 | bill.sayName = jane.sayName; 14 | bill.sayName(); // ? 15 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes5.lox.expected: -------------------------------------------------------------------------------- 1 | Jane 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes6.lox: -------------------------------------------------------------------------------- 1 | class Bacon { 2 | eat() { 3 | print "Crunch crunch crunch!"; 4 | } 5 | } 6 | 7 | Bacon().eat(); // Prints "Crunch crunch crunch!". 8 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes6.lox.expected: -------------------------------------------------------------------------------- 1 | Crunch crunch crunch! 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes7.lox: -------------------------------------------------------------------------------- 1 | class Egotist { 2 | speak() { 3 | print this; 4 | } 5 | } 6 | 7 | var method = Egotist().speak; 8 | method(); 9 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes7.lox.expected: -------------------------------------------------------------------------------- 1 | Egotist instance 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes8.lox: -------------------------------------------------------------------------------- 1 | class Cake { 2 | taste() { 3 | var adjective = "delicious"; 4 | print "The " + this.flavor + " cake is " + adjective + "!"; 5 | } 6 | } 7 | 8 | var cake = Cake(); 9 | cake.flavor = "German chocolate"; 10 | cake.taste(); // Prints "The German chocolate cake is delicious!". 11 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes8.lox.expected: -------------------------------------------------------------------------------- 1 | The German chocolate cake is delicious! 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes9.lox: -------------------------------------------------------------------------------- 1 | class Thing { 2 | getCallback() { 3 | fun localFunction() { 4 | print this; 5 | } 6 | 7 | return localFunction; 8 | } 9 | } 10 | 11 | var callback = Thing().getCallback(); 12 | callback(); 13 | -------------------------------------------------------------------------------- /chapter12/tests/test-classes9.lox.expected: -------------------------------------------------------------------------------- 1 | Thing instance 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-control-flow.lox: -------------------------------------------------------------------------------- 1 | print "hi" or 2; // "hi". 2 | print nil or "yes"; // "yes". 3 | -------------------------------------------------------------------------------- /chapter12/tests/test-control-flow.lox.expected: -------------------------------------------------------------------------------- 1 | hi 2 | yes 3 | -------------------------------------------------------------------------------- /chapter12/tests/test-control-flow2.lox: -------------------------------------------------------------------------------- 1 | var a = 0; 2 | var temp; 3 | 4 | for (var b = 1; a < 10000; b = temp + b) { 5 | print a; 6 | temp = a; 7 | a = b; 8 | } 9 | -------------------------------------------------------------------------------- /chapter12/tests/test-control-flow2.lox.expected: -------------------------------------------------------------------------------- 1 | 0.000000 2 | 1.000000 3 | 1.000000 4 | 2.000000 5 | 3.000000 6 | 5.000000 7 | 8.000000 8 | 13.000000 9 | 21.000000 10 | 34.000000 11 | 55.000000 12 | 89.000000 13 | 144.000000 14 | 233.000000 15 | 377.000000 16 | 610.000000 17 | 987.000000 18 | 1597.000000 19 | 2584.000000 20 | 4181.000000 21 | 6765.000000 22 | -------------------------------------------------------------------------------- /chapter12/tests/test-functions.lox: -------------------------------------------------------------------------------- 1 | fun add(a, b) { 2 | print a + b; 3 | } 4 | 5 | print add; // "". 6 | -------------------------------------------------------------------------------- /chapter12/tests/test-functions.lox.expected: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-functions2.lox: -------------------------------------------------------------------------------- 1 | fun sayHi(first, last) { 2 | print "Hi, " + first + " " + last + "!"; 3 | } 4 | 5 | sayHi("Dear", "Reader"); 6 | -------------------------------------------------------------------------------- /chapter12/tests/test-functions2.lox.expected: -------------------------------------------------------------------------------- 1 | Hi, Dear Reader! 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-functions3.lox: -------------------------------------------------------------------------------- 1 | fun fib(n) { 2 | if (n <= 1) return n; 3 | return fib(n - 2) + fib(n - 1); 4 | } 5 | 6 | for (var i = 0; i < 20; i = i + 1) { 7 | print fib(i); 8 | } 9 | -------------------------------------------------------------------------------- /chapter12/tests/test-functions3.lox.expected: -------------------------------------------------------------------------------- 1 | 0.000000 2 | 1.000000 3 | 1.000000 4 | 2.000000 5 | 3.000000 6 | 5.000000 7 | 8.000000 8 | 13.000000 9 | 21.000000 10 | 34.000000 11 | 55.000000 12 | 89.000000 13 | 144.000000 14 | 233.000000 15 | 377.000000 16 | 610.000000 17 | 987.000000 18 | 1597.000000 19 | 2584.000000 20 | 4181.000000 21 | -------------------------------------------------------------------------------- /chapter12/tests/test-functions4.lox: -------------------------------------------------------------------------------- 1 | fun makeCounter() { 2 | var i = 0; 3 | fun count() { 4 | i = i + 1; 5 | print i; 6 | } 7 | 8 | return count; 9 | } 10 | 11 | var counter = makeCounter(); 12 | counter(); // "1". 13 | counter(); // "2". 14 | -------------------------------------------------------------------------------- /chapter12/tests/test-functions4.lox.expected: -------------------------------------------------------------------------------- 1 | 1.000000 2 | 2.000000 3 | -------------------------------------------------------------------------------- /chapter12/tests/test-resolving.lox: -------------------------------------------------------------------------------- 1 | var a = "global"; 2 | { 3 | fun showA() { 4 | print a; 5 | } 6 | 7 | showA(); 8 | var a = "block"; 9 | showA(); 10 | } 11 | -------------------------------------------------------------------------------- /chapter12/tests/test-resolving.lox.expected: -------------------------------------------------------------------------------- 1 | global 2 | global 3 | -------------------------------------------------------------------------------- /chapter12/tests/test-resolving2.lox: -------------------------------------------------------------------------------- 1 | var a = "outer"; 2 | { 3 | var a = a; 4 | } 5 | -------------------------------------------------------------------------------- /chapter12/tests/test-resolving2.lox.expected: -------------------------------------------------------------------------------- 1 | [line 3] Error at 'a': Can't read local variable in its own initializer. 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-resolving3.lox: -------------------------------------------------------------------------------- 1 | fun bad() { 2 | var a = "first"; 3 | var a = "second"; 4 | } 5 | -------------------------------------------------------------------------------- /chapter12/tests/test-resolving3.lox.expected: -------------------------------------------------------------------------------- 1 | [line 3] Error at 'a': Already a variable with this name in this scope. 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-resolving4.lox: -------------------------------------------------------------------------------- 1 | return "at top level"; 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-resolving4.lox.expected: -------------------------------------------------------------------------------- 1 | [line 1] Error at 'return': Can't return from top-level code. 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-statements.lox: -------------------------------------------------------------------------------- 1 | print "one"; 2 | print true; 3 | print 2 + 1; 4 | -------------------------------------------------------------------------------- /chapter12/tests/test-statements.lox.expected: -------------------------------------------------------------------------------- 1 | one 2 | true 3 | 3.000000 4 | -------------------------------------------------------------------------------- /chapter12/tests/test-statements2.lox: -------------------------------------------------------------------------------- 1 | var a = "before"; 2 | print a; // "before". 3 | var a = "after"; 4 | print a; // "after". 5 | -------------------------------------------------------------------------------- /chapter12/tests/test-statements2.lox.expected: -------------------------------------------------------------------------------- 1 | before 2 | after 3 | -------------------------------------------------------------------------------- /chapter12/tests/test-statements3.lox: -------------------------------------------------------------------------------- 1 | var a; 2 | print a; // "nil". 3 | -------------------------------------------------------------------------------- /chapter12/tests/test-statements3.lox.expected: -------------------------------------------------------------------------------- 1 | nil 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-statements4.lox: -------------------------------------------------------------------------------- 1 | var a = 1; 2 | var b = 2; 3 | print a + b; 4 | -------------------------------------------------------------------------------- /chapter12/tests/test-statements4.lox.expected: -------------------------------------------------------------------------------- 1 | 3.000000 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-statements5.lox: -------------------------------------------------------------------------------- 1 | var a = 1; 2 | print a = 2; // "2". 3 | -------------------------------------------------------------------------------- /chapter12/tests/test-statements5.lox.expected: -------------------------------------------------------------------------------- 1 | 2.000000 2 | -------------------------------------------------------------------------------- /chapter12/tests/test-statements6.lox: -------------------------------------------------------------------------------- 1 | var a = "global a"; 2 | var b = "global b"; 3 | var c = "global c"; 4 | { 5 | var a = "outer a"; 6 | var b = "outer b"; 7 | { 8 | var a = "inner a"; 9 | print a; 10 | print b; 11 | print c; 12 | } 13 | print a; 14 | print b; 15 | print c; 16 | } 17 | print a; 18 | print b; 19 | print c; 20 | -------------------------------------------------------------------------------- /chapter12/tests/test-statements6.lox.expected: -------------------------------------------------------------------------------- 1 | inner a 2 | outer b 3 | global c 4 | outer a 5 | outer b 6 | global c 7 | global a 8 | global b 9 | global c 10 | -------------------------------------------------------------------------------- /chapter13/AstPrinter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "Expr.h" 11 | 12 | class AstPrinter: public ExprVisitor { 13 | public: 14 | std::string print(std::shared_ptr expr) { 15 | return std::any_cast(expr->accept(*this)); 16 | } 17 | 18 | std::any visitBinaryExpr(std::shared_ptr expr) override { 19 | return parenthesize(expr->op.lexeme, 20 | expr->left, expr->right); 21 | } 22 | 23 | std::any visitGroupingExpr( 24 | std::shared_ptr expr) override { 25 | return parenthesize("group", expr->expression); 26 | } 27 | 28 | std::any visitLiteralExpr(std::shared_ptr expr) override { 29 | auto& value_type = expr->value.type(); 30 | 31 | if (value_type == typeid(nullptr)) { 32 | return "nil"; 33 | } else if (value_type == typeid(std::string)) { 34 | return std::any_cast(expr->value); 35 | } else if (value_type == typeid(double)) { 36 | return std::to_string(std::any_cast(expr->value)); 37 | } else if (value_type == typeid(bool)) { 38 | return std::any_cast(expr->value) ? "true" : "false"; 39 | } 40 | 41 | return "Error in visitLiteralExpr: literal type not recognized."; 42 | } 43 | 44 | std::any visitUnaryExpr(std::shared_ptr expr) override { 45 | return parenthesize(expr->op.lexeme, expr->right); 46 | } 47 | 48 | private: 49 | template 50 | std::string parenthesize(std::string_view name, E... expr) 51 | { 52 | assert((... && std::is_same_v>)); 53 | 54 | std::ostringstream builder; 55 | 56 | builder << "(" << name; 57 | ((builder << " " << print(expr)), ...); 58 | builder << ")"; 59 | 60 | return builder.str(); 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /chapter13/AstPrinterDriver.cpp: -------------------------------------------------------------------------------- 1 | #include "AstPrinter.h" 2 | 3 | int main(int argc, char* argv[]) { 4 | std::shared_ptr expression = std::make_shared( 5 | std::make_shared( 6 | Token{MINUS, "-", nullptr, 1}, 7 | std::make_shared(123.) 8 | ), 9 | Token{STAR, "*", nullptr, 1}, 10 | std::make_shared( 11 | std::make_shared(45.67))); 12 | 13 | std::cout << AstPrinter{}.print(expression) << "\n"; 14 | } -------------------------------------------------------------------------------- /chapter13/Environment.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include // less 5 | #include 6 | #include 7 | #include 8 | #include // std::move 9 | #include "Error.h" 10 | #include "Token.h" 11 | 12 | class Environment: public std::enable_shared_from_this { 13 | friend class Interpreter; 14 | 15 | std::shared_ptr enclosing; 16 | std::map values; 17 | 18 | public: 19 | Environment() 20 | : enclosing{nullptr} 21 | {} 22 | 23 | Environment(std::shared_ptr enclosing) 24 | : enclosing{std::move(enclosing)} 25 | {} 26 | 27 | std::any get(const Token& name) { 28 | auto elem = values.find(name.lexeme); 29 | if (elem != values.end()) { 30 | return elem->second; 31 | } 32 | 33 | if (enclosing != nullptr) return enclosing->get(name); 34 | 35 | throw RuntimeError(name, 36 | "Undefined variable '" + name.lexeme + "'."); 37 | } 38 | 39 | void assign(const Token& name, std::any value) { 40 | auto elem = values.find(name.lexeme); 41 | if (elem != values.end()) { 42 | elem->second = std::move(value); 43 | return; 44 | } 45 | 46 | if (enclosing != nullptr) { 47 | enclosing->assign(name, std::move(value)); 48 | return; 49 | } 50 | 51 | throw RuntimeError(name, 52 | "Undefined variable '" + name.lexeme + "'."); 53 | } 54 | 55 | void define(const std::string& name, std::any value) { 56 | values[name] = std::move(value); 57 | } 58 | 59 | std::shared_ptr ancestor(int distance) { 60 | std::shared_ptr environment = shared_from_this(); 61 | for (int i = 0; i < distance; ++i) { 62 | environment = environment->enclosing; 63 | } 64 | 65 | return environment; 66 | } 67 | 68 | std::any getAt(int distance, const std::string& name) { 69 | return ancestor(distance)->values[name]; 70 | } 71 | 72 | void assignAt(int distance, const Token& name, std::any value) { 73 | ancestor(distance)->values[name.lexeme] = std::move(value); 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /chapter13/Error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "RuntimeError.h" 6 | #include "Token.h" 7 | 8 | inline bool hadError = false; 9 | inline bool hadRuntimeError = false; 10 | 11 | static void report(int line, std::string_view where, 12 | std::string_view message) { 13 | std::cerr << 14 | "[line " << line << "] Error" << where << ": " << message << 15 | "\n"; 16 | hadError = true; 17 | } 18 | 19 | void error(const Token& token, std::string_view message) { 20 | if (token.type == END_OF_FILE) { 21 | report(token.line, " at end", message); 22 | } else { 23 | report(token.line, " at '" + token.lexeme + "'", message); 24 | } 25 | } 26 | 27 | void error(int line, std::string_view message) { 28 | report(line, "", message); 29 | } 30 | 31 | void runtimeError(const RuntimeError& error) { 32 | std::cerr << error.what() << 33 | "\n[line " << error.token.line << "]\n"; 34 | hadRuntimeError = true; 35 | } 36 | -------------------------------------------------------------------------------- /chapter13/Lox.cpp: -------------------------------------------------------------------------------- 1 | #include // std::strerror 2 | #include // readFile 3 | #include // std::getline 4 | #include 5 | #include 6 | #include "Error.h" 7 | #include "Interpreter.h" 8 | #include "Parser.h" 9 | #include "Resolver.h" 10 | #include "Scanner.h" 11 | 12 | // It's not good practice to include .cpp files, but in our case it 13 | // allows us to lay out the files similarly to the Java code while 14 | // avoiding circular dependencies. 15 | #include "LoxFunction.cpp" // Chapter 10 - Functions 16 | #include "LoxClass.cpp" // Chapter 12 - Classes 17 | #include "LoxInstance.cpp" // Chapter 12 - Classes 18 | 19 | std::string readFile(std::string_view path) { 20 | std::ifstream file{path.data(), std::ios::in | std::ios::binary | 21 | std::ios::ate}; 22 | if (!file) { 23 | std::cerr << "Failed to open file " << path << ": " 24 | << std::strerror(errno) << "\n"; 25 | std::exit(74); 26 | } 27 | 28 | std::string contents; 29 | contents.resize(file.tellg()); 30 | 31 | file.seekg(0, std::ios::beg); 32 | file.read(contents.data(), contents.size()); 33 | 34 | return contents; 35 | } 36 | 37 | Interpreter interpreter{}; 38 | 39 | void run(std::string_view source) { 40 | Scanner scanner {source}; 41 | std::vector tokens = scanner.scanTokens(); 42 | Parser parser{tokens}; 43 | std::vector> statements = parser.parse(); 44 | 45 | // Stop if there was a syntax error. 46 | if (hadError) return; 47 | 48 | Resolver resolver{interpreter}; 49 | resolver.resolve(statements); 50 | 51 | // Stop if there was a resolution error. 52 | if (hadError) return; 53 | 54 | interpreter.interpret(statements); 55 | } 56 | 57 | void runFile(std::string_view path) { 58 | std::string contents = readFile(path); 59 | run(contents); 60 | 61 | // Indicate an error in the exit code. 62 | if (hadError) std::exit(65); 63 | if (hadRuntimeError) std::exit(70); 64 | } 65 | 66 | void runPrompt() { 67 | for (;;) { 68 | std::cout << "> "; 69 | std::string line; 70 | if (!std::getline(std::cin, line)) break; 71 | run(line); 72 | hadError = false; 73 | } 74 | } 75 | 76 | int main(int argc, char* argv[]) { 77 | if (argc > 2) { 78 | std::cout << "Usage: jlox [script]\n"; 79 | std::exit(64); 80 | } else if (argc == 2) { 81 | runFile(argv[1]); 82 | } else { 83 | runPrompt(); 84 | } 85 | } -------------------------------------------------------------------------------- /chapter13/LoxCallable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class Interpreter; 8 | 9 | class LoxCallable { 10 | public: 11 | virtual int arity() = 0; 12 | virtual std::any call(Interpreter& interpreter, 13 | std::vector arguments) = 0; 14 | virtual std::string toString() = 0; 15 | virtual ~LoxCallable() = default; 16 | }; -------------------------------------------------------------------------------- /chapter13/LoxClass.cpp: -------------------------------------------------------------------------------- 1 | #include "LoxClass.h" 2 | #include // std::move 3 | 4 | LoxClass::LoxClass(std::string name, 5 | std::shared_ptr superclass, 6 | std::map> methods) 7 | : superclass{superclass}, name{std::move(name)}, 8 | methods{std::move(methods)} 9 | {} 10 | 11 | std::shared_ptr LoxClass::findMethod( 12 | const std::string& name) { 13 | auto elem = methods.find(name); 14 | if (elem != methods.end()) { 15 | return elem->second; 16 | } 17 | 18 | if (superclass != nullptr) { 19 | return superclass->findMethod(name); 20 | } 21 | 22 | return nullptr; 23 | } 24 | 25 | std::string LoxClass::toString() { 26 | return name; 27 | } 28 | 29 | std::any LoxClass::call(Interpreter& interpreter, 30 | std::vector arguments) { 31 | auto instance = std::make_shared(shared_from_this()); 32 | std::shared_ptr initializer = findMethod("init"); 33 | if (initializer != nullptr) { 34 | initializer->bind(instance)->call(interpreter, 35 | std::move(arguments)); 36 | } 37 | 38 | return instance; 39 | } 40 | 41 | int LoxClass::arity() { 42 | std::shared_ptr initializer = findMethod("init"); 43 | if (initializer == nullptr) return 0; 44 | return initializer->arity(); 45 | } 46 | -------------------------------------------------------------------------------- /chapter13/LoxClass.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "LoxCallable.h" 9 | 10 | class Interpreter; 11 | class LoxFunction; 12 | 13 | class LoxClass: public LoxCallable, 14 | public std::enable_shared_from_this { 15 | friend class LoxInstance; 16 | const std::string name; 17 | const std::shared_ptr superclass; 18 | std::map> methods; 19 | 20 | public: 21 | LoxClass(std::string name, std::shared_ptr superclass, 22 | std::map> methods); 23 | 24 | std::shared_ptr findMethod(const std::string& name); 25 | std::string toString() override; 26 | std::any call(Interpreter& interpreter, 27 | std::vector arguments) override; 28 | int arity() override; 29 | }; 30 | -------------------------------------------------------------------------------- /chapter13/LoxFunction.cpp: -------------------------------------------------------------------------------- 1 | #include "LoxFunction.h" 2 | #include // std::move 3 | #include "Environment.h" 4 | #include "LoxInstance.h" 5 | #include "Interpreter.h" 6 | #include "Stmt.h" 7 | 8 | LoxFunction::LoxFunction(std::shared_ptr declaration, 9 | std::shared_ptr closure, 10 | bool isInitializer) 11 | : isInitializer{isInitializer}, closure{std::move(closure)}, 12 | declaration{std::move(declaration)} 13 | {} 14 | 15 | std::shared_ptr LoxFunction::bind( 16 | std::shared_ptr instance) { 17 | auto environment = std::make_shared(closure); 18 | environment->define("this", instance); 19 | return std::make_shared(declaration, environment, 20 | isInitializer); 21 | } 22 | 23 | std::string LoxFunction::toString() { 24 | return "name.lexeme + ">"; 25 | } 26 | 27 | int LoxFunction::arity() { 28 | return declaration->params.size(); 29 | } 30 | 31 | std::any LoxFunction::call(Interpreter& interpreter, 32 | std::vector arguments) { 33 | auto environment = std::make_shared(closure); 34 | for (int i = 0; i < declaration->params.size(); ++i) { 35 | environment->define(declaration->params[i].lexeme, 36 | arguments[i]); 37 | } 38 | 39 | try { 40 | interpreter.executeBlock(declaration->body, environment); 41 | } catch (LoxReturn returnValue) { 42 | if (isInitializer) return closure->getAt(0, "this"); 43 | 44 | return returnValue.value; 45 | } 46 | 47 | if (isInitializer) return closure->getAt(0, "this"); 48 | 49 | return nullptr; 50 | } 51 | -------------------------------------------------------------------------------- /chapter13/LoxFunction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "LoxCallable.h" 8 | 9 | class Environment; 10 | class Function; 11 | class LoxInstance; 12 | 13 | class LoxFunction: public LoxCallable { 14 | std::shared_ptr declaration; 15 | std::shared_ptr closure; 16 | 17 | bool isInitializer; 18 | 19 | public: 20 | LoxFunction(std::shared_ptr declaration, 21 | std::shared_ptr closure, 22 | bool isInitializer); 23 | std::shared_ptr bind( 24 | std::shared_ptr instance); 25 | std::string toString() override; 26 | int arity() override; 27 | std::any call(Interpreter& interpreter, 28 | std::vector arguments) override; 29 | }; 30 | -------------------------------------------------------------------------------- /chapter13/LoxInstance.cpp: -------------------------------------------------------------------------------- 1 | #include "LoxInstance.h" 2 | #include // std::move 3 | #include "Error.h" 4 | 5 | LoxInstance::LoxInstance(std::shared_ptr klass) 6 | : klass{std::move(klass)} 7 | {} 8 | 9 | std::any LoxInstance::get(const Token& name) { 10 | auto elem = fields.find(name.lexeme); 11 | if (elem != fields.end()) { 12 | return elem->second; 13 | } 14 | 15 | std::shared_ptr method = 16 | klass->findMethod(name.lexeme); 17 | if (method != nullptr) return method->bind(shared_from_this()); 18 | 19 | throw RuntimeError(name, 20 | "Undefined property '" + name.lexeme + "'."); 21 | } 22 | 23 | void LoxInstance::set(const Token& name, std::any value) { 24 | fields[name.lexeme] = std::move(value); 25 | } 26 | 27 | std::string LoxInstance::toString() { 28 | return klass->name + " instance"; 29 | } 30 | -------------------------------------------------------------------------------- /chapter13/LoxInstance.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class LoxClass; 9 | class Token; 10 | 11 | class LoxInstance: public std::enable_shared_from_this { 12 | std::shared_ptr klass; 13 | std::map fields; 14 | 15 | public: 16 | LoxInstance(std::shared_ptr klass); 17 | std::any get(const Token& name); 18 | void set(const Token& name, std::any value); 19 | std::string toString(); 20 | }; 21 | -------------------------------------------------------------------------------- /chapter13/LoxReturn.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct LoxReturn { 6 | const std::any value; 7 | }; -------------------------------------------------------------------------------- /chapter13/Makefile: -------------------------------------------------------------------------------- 1 | CXX := g++ 2 | CXXFLAGS := -ggdb -std=c++17 3 | CPPFLAGS := -MMD 4 | 5 | COMPILE := $(CXX) $(CXXFLAGS) $(CPPFLAGS) 6 | 7 | SRCS := AstPrinterDriver.cpp GenerateAst.cpp Lox.cpp 8 | DEPS := $(SRCS:.cpp=.d) 9 | 10 | 11 | jlox: Expr.h Stmt.h Lox.o 12 | @$(COMPILE) Lox.o -o $@ 13 | 14 | 15 | ast_printer: Expr.h Stmt.h AstPrinterDriver.o 16 | @$(COMPILE) AstPrinterDriver.o -o $@ 17 | 18 | 19 | generate_ast: GenerateAst.o 20 | @$(COMPILE) $< -o $@ 21 | 22 | 23 | Expr.h Stmt.h: generate_ast 24 | @./generate_ast . 25 | 26 | 27 | .PHONY: clean 28 | clean: 29 | rm -f *.d *.o ast_printer generate_ast jlox 30 | 31 | 32 | -include $(DEPS) 33 | 34 | 35 | define make_test 36 | .PHONY: $(1) 37 | $(1): 38 | @make jlox >/dev/null 39 | @echo "testing jlox with $(1).lox ..." 40 | @./jlox tests/$(1).lox | diff -u --color tests/$(1).lox.expected -; 41 | endef 42 | 43 | 44 | define make_test_error 45 | .PHONY: $(1) 46 | $(1): 47 | @make jlox >/dev/null 48 | @echo "testing jlox with $(1).lox ..." 49 | @./jlox tests/$(1).lox 2>&1 | diff -u --color tests/$(1).lox.expected -; 50 | endef 51 | 52 | 53 | TESTS = \ 54 | test-statements \ 55 | test-statements2 \ 56 | test-statements3 \ 57 | test-statements4 \ 58 | test-statements5 \ 59 | test-statements6 \ 60 | test-control-flow \ 61 | test-control-flow2 \ 62 | test-functions \ 63 | test-functions2 \ 64 | test-functions3 \ 65 | test-functions4 \ 66 | test-resolving \ 67 | test-classes \ 68 | test-classes2 \ 69 | test-classes3 \ 70 | test-classes4 \ 71 | test-classes5 \ 72 | test-classes6 \ 73 | test-classes7 \ 74 | test-classes8 \ 75 | test-classes9 \ 76 | test-classes12 \ 77 | test-inheritance2 \ 78 | test-inheritance3 \ 79 | test-inheritance5 \ 80 | 81 | 82 | TEST_ERRORS = \ 83 | test-resolving2 \ 84 | test-resolving3 \ 85 | test-resolving4 \ 86 | test-classes10 \ 87 | test-classes11 \ 88 | test-classes13 \ 89 | test-inheritance \ 90 | test-inheritance4 \ 91 | test-inheritance6 \ 92 | test-inheritance7 93 | 94 | 95 | $(foreach test, $(TESTS), $(eval $(call make_test,$(test)))) 96 | $(foreach test, $(TEST_ERRORS), $(eval $(call make_test_error,$(test)))) 97 | 98 | 99 | .PHONY: test-all 100 | test-all: 101 | @for test in $(TESTS) $(TEST_ERRORS); do \ 102 | make -s $$test; \ 103 | done 104 | -------------------------------------------------------------------------------- /chapter13/RuntimeError.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Token.h" 5 | 6 | class RuntimeError: public std::runtime_error { 7 | public: 8 | const Token& token; 9 | 10 | RuntimeError(const Token& token, std::string_view message) 11 | : std::runtime_error{message.data()}, token{token} 12 | {} 13 | }; -------------------------------------------------------------------------------- /chapter13/Token.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include // std::move 6 | #include "TokenType.h" 7 | 8 | class Token { 9 | public: 10 | const TokenType type; 11 | const std::string lexeme; 12 | const std::any literal; 13 | const int line; 14 | 15 | Token(TokenType type, std::string lexeme, std::any literal, 16 | int line) 17 | : type{type}, lexeme{std::move(lexeme)}, 18 | literal{std::move(literal)}, line{line} 19 | {} 20 | 21 | std::string toString() const { 22 | std::string literal_text; 23 | 24 | switch (type) { 25 | case (IDENTIFIER): 26 | literal_text = lexeme; 27 | break; 28 | case (STRING): 29 | literal_text = std::any_cast(literal); 30 | break; 31 | case (NUMBER): 32 | literal_text = std::to_string(std::any_cast(literal)); 33 | break; 34 | case (TRUE): 35 | literal_text = "true"; 36 | break; 37 | case (FALSE): 38 | literal_text = "false"; 39 | break; 40 | default: 41 | literal_text = "nil"; 42 | } 43 | 44 | return ::toString(type) + " " + lexeme + " " + literal_text; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /chapter13/TokenType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | enum TokenType { 6 | // Single-character tokens. 7 | LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, 8 | COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, 9 | 10 | // One or two character tokens. 11 | BANG, BANG_EQUAL, 12 | EQUAL, EQUAL_EQUAL, 13 | GREATER, GREATER_EQUAL, 14 | LESS, LESS_EQUAL, 15 | 16 | // Literals. 17 | IDENTIFIER, STRING, NUMBER, 18 | 19 | // Keywords. 20 | AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR, 21 | PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE, 22 | 23 | END_OF_FILE 24 | }; 25 | 26 | std::string toString(TokenType type) { 27 | static const std::string strings[] = { 28 | "LEFT_PAREN", "RIGHT_PAREN", "LEFT_BRACE", "RIGHT_BRACE", 29 | "COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON", "SLASH", "STAR", 30 | "BANG", "BANG_EQUAL", 31 | "EQUAL", "EQUAL_EQUAL", 32 | "GREATER", "GREATER_EQUAL", 33 | "LESS", "LESS_EQUAL", 34 | "IDENTIFIER", "STRING", "NUMBER", 35 | "AND", "CLASS", "ELSE", "FALSE", "FUN", "FOR", "IF", "NIL", "OR", 36 | "PRINT", "RETURN", "SUPER", "THIS", "TRUE", "VAR", "WHILE", 37 | "END_OF_FILE" 38 | }; 39 | 40 | return strings[static_cast(type)]; 41 | } 42 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes.lox: -------------------------------------------------------------------------------- 1 | class DevonshireCream { 2 | serveOn() { 3 | return "Scones"; 4 | } 5 | } 6 | 7 | print DevonshireCream; // Prints "DevonshireCream". 8 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes.lox.expected: -------------------------------------------------------------------------------- 1 | DevonshireCream 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes10.lox: -------------------------------------------------------------------------------- 1 | print this; 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes10.lox.expected: -------------------------------------------------------------------------------- 1 | [line 1] Error at 'this': Can't use 'this' outside of a class. 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes11.lox: -------------------------------------------------------------------------------- 1 | fun notAMethod() { 2 | print this; 3 | } 4 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes11.lox.expected: -------------------------------------------------------------------------------- 1 | [line 2] Error at 'this': Can't use 'this' outside of a class. 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes12.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | init() { 3 | print this; 4 | } 5 | } 6 | 7 | var foo = Foo(); 8 | print foo.init(); 9 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes12.lox.expected: -------------------------------------------------------------------------------- 1 | Foo instance 2 | Foo instance 3 | Foo instance 4 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes13.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | init() { 3 | return "something else"; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes13.lox.expected: -------------------------------------------------------------------------------- 1 | [line 3] Error at 'return': Can't return a value from an initializer. 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes2.lox: -------------------------------------------------------------------------------- 1 | class Bagel {} 2 | var bagel = Bagel(); 3 | print bagel; // Prints "Bagel instance". 4 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes2.lox.expected: -------------------------------------------------------------------------------- 1 | Bagel instance 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes3.lox: -------------------------------------------------------------------------------- 1 | class Box {} 2 | 3 | fun notMethod(argument) { 4 | print "called function with " + argument; 5 | } 6 | 7 | var box = Box(); 8 | box.function = notMethod; 9 | box.function("argument"); 10 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes3.lox.expected: -------------------------------------------------------------------------------- 1 | called function with argument 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes4.lox: -------------------------------------------------------------------------------- 1 | class Person { 2 | sayName() { 3 | print this.name; 4 | } 5 | } 6 | 7 | var jane = Person(); 8 | jane.name = "Jane"; 9 | 10 | var method = jane.sayName; 11 | method(); // ? 12 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes4.lox.expected: -------------------------------------------------------------------------------- 1 | Jane 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes5.lox: -------------------------------------------------------------------------------- 1 | class Person { 2 | sayName() { 3 | print this.name; 4 | } 5 | } 6 | 7 | var jane = Person(); 8 | jane.name = "Jane"; 9 | 10 | var bill = Person(); 11 | bill.name = "Bill"; 12 | 13 | bill.sayName = jane.sayName; 14 | bill.sayName(); // ? 15 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes5.lox.expected: -------------------------------------------------------------------------------- 1 | Jane 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes6.lox: -------------------------------------------------------------------------------- 1 | class Bacon { 2 | eat() { 3 | print "Crunch crunch crunch!"; 4 | } 5 | } 6 | 7 | Bacon().eat(); // Prints "Crunch crunch crunch!". 8 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes6.lox.expected: -------------------------------------------------------------------------------- 1 | Crunch crunch crunch! 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes7.lox: -------------------------------------------------------------------------------- 1 | class Egotist { 2 | speak() { 3 | print this; 4 | } 5 | } 6 | 7 | var method = Egotist().speak; 8 | method(); 9 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes7.lox.expected: -------------------------------------------------------------------------------- 1 | Egotist instance 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes8.lox: -------------------------------------------------------------------------------- 1 | class Cake { 2 | taste() { 3 | var adjective = "delicious"; 4 | print "The " + this.flavor + " cake is " + adjective + "!"; 5 | } 6 | } 7 | 8 | var cake = Cake(); 9 | cake.flavor = "German chocolate"; 10 | cake.taste(); // Prints "The German chocolate cake is delicious!". 11 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes8.lox.expected: -------------------------------------------------------------------------------- 1 | The German chocolate cake is delicious! 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes9.lox: -------------------------------------------------------------------------------- 1 | class Thing { 2 | getCallback() { 3 | fun localFunction() { 4 | print this; 5 | } 6 | 7 | return localFunction; 8 | } 9 | } 10 | 11 | var callback = Thing().getCallback(); 12 | callback(); 13 | -------------------------------------------------------------------------------- /chapter13/tests/test-classes9.lox.expected: -------------------------------------------------------------------------------- 1 | Thing instance 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-control-flow.lox: -------------------------------------------------------------------------------- 1 | print "hi" or 2; // "hi". 2 | print nil or "yes"; // "yes". 3 | -------------------------------------------------------------------------------- /chapter13/tests/test-control-flow.lox.expected: -------------------------------------------------------------------------------- 1 | hi 2 | yes 3 | -------------------------------------------------------------------------------- /chapter13/tests/test-control-flow2.lox: -------------------------------------------------------------------------------- 1 | var a = 0; 2 | var temp; 3 | 4 | for (var b = 1; a < 10000; b = temp + b) { 5 | print a; 6 | temp = a; 7 | a = b; 8 | } 9 | -------------------------------------------------------------------------------- /chapter13/tests/test-control-flow2.lox.expected: -------------------------------------------------------------------------------- 1 | 0.000000 2 | 1.000000 3 | 1.000000 4 | 2.000000 5 | 3.000000 6 | 5.000000 7 | 8.000000 8 | 13.000000 9 | 21.000000 10 | 34.000000 11 | 55.000000 12 | 89.000000 13 | 144.000000 14 | 233.000000 15 | 377.000000 16 | 610.000000 17 | 987.000000 18 | 1597.000000 19 | 2584.000000 20 | 4181.000000 21 | 6765.000000 22 | -------------------------------------------------------------------------------- /chapter13/tests/test-functions.lox: -------------------------------------------------------------------------------- 1 | fun add(a, b) { 2 | print a + b; 3 | } 4 | 5 | print add; // "". 6 | -------------------------------------------------------------------------------- /chapter13/tests/test-functions.lox.expected: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-functions2.lox: -------------------------------------------------------------------------------- 1 | fun sayHi(first, last) { 2 | print "Hi, " + first + " " + last + "!"; 3 | } 4 | 5 | sayHi("Dear", "Reader"); 6 | -------------------------------------------------------------------------------- /chapter13/tests/test-functions2.lox.expected: -------------------------------------------------------------------------------- 1 | Hi, Dear Reader! 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-functions3.lox: -------------------------------------------------------------------------------- 1 | fun fib(n) { 2 | if (n <= 1) return n; 3 | return fib(n - 2) + fib(n - 1); 4 | } 5 | 6 | for (var i = 0; i < 20; i = i + 1) { 7 | print fib(i); 8 | } 9 | -------------------------------------------------------------------------------- /chapter13/tests/test-functions3.lox.expected: -------------------------------------------------------------------------------- 1 | 0.000000 2 | 1.000000 3 | 1.000000 4 | 2.000000 5 | 3.000000 6 | 5.000000 7 | 8.000000 8 | 13.000000 9 | 21.000000 10 | 34.000000 11 | 55.000000 12 | 89.000000 13 | 144.000000 14 | 233.000000 15 | 377.000000 16 | 610.000000 17 | 987.000000 18 | 1597.000000 19 | 2584.000000 20 | 4181.000000 21 | -------------------------------------------------------------------------------- /chapter13/tests/test-functions4.lox: -------------------------------------------------------------------------------- 1 | fun makeCounter() { 2 | var i = 0; 3 | fun count() { 4 | i = i + 1; 5 | print i; 6 | } 7 | 8 | return count; 9 | } 10 | 11 | var counter = makeCounter(); 12 | counter(); // "1". 13 | counter(); // "2". 14 | -------------------------------------------------------------------------------- /chapter13/tests/test-functions4.lox.expected: -------------------------------------------------------------------------------- 1 | 1.000000 2 | 2.000000 3 | -------------------------------------------------------------------------------- /chapter13/tests/test-inheritance.lox: -------------------------------------------------------------------------------- 1 | var NotAClass = "I am totally not a class"; 2 | 3 | class Subclass < NotAClass {} // ?! 4 | -------------------------------------------------------------------------------- /chapter13/tests/test-inheritance.lox.expected: -------------------------------------------------------------------------------- 1 | Superclass must be a class. 2 | [line 3] 3 | -------------------------------------------------------------------------------- /chapter13/tests/test-inheritance2.lox: -------------------------------------------------------------------------------- 1 | class Doughnut { 2 | cook() { 3 | print "Fry until golden brown."; 4 | } 5 | } 6 | 7 | class BostonCream < Doughnut {} 8 | 9 | BostonCream().cook(); 10 | -------------------------------------------------------------------------------- /chapter13/tests/test-inheritance2.lox.expected: -------------------------------------------------------------------------------- 1 | Fry until golden brown. 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-inheritance3.lox: -------------------------------------------------------------------------------- 1 | class Doughnut { 2 | cook() { 3 | print "Fry until golden brown."; 4 | } 5 | } 6 | 7 | class BostonCream < Doughnut { 8 | cook() { 9 | super.cook(); 10 | print "Pipe full of custard and coat with chocolate."; 11 | } 12 | } 13 | 14 | BostonCream().cook(); 15 | -------------------------------------------------------------------------------- /chapter13/tests/test-inheritance3.lox.expected: -------------------------------------------------------------------------------- 1 | Fry until golden brown. 2 | Pipe full of custard and coat with chocolate. 3 | -------------------------------------------------------------------------------- /chapter13/tests/test-inheritance4.lox: -------------------------------------------------------------------------------- 1 | print super; // Syntax error. 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-inheritance4.lox.expected: -------------------------------------------------------------------------------- 1 | [line 1] Error at ';': Expect '.' after 'super'. 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-inheritance5.lox: -------------------------------------------------------------------------------- 1 | class A { 2 | method() { 3 | print "Method A"; 4 | } 5 | } 6 | 7 | class B < A { 8 | method() { 9 | print "Method B"; 10 | } 11 | 12 | test() { 13 | super.method(); 14 | } 15 | } 16 | 17 | class C < B {} 18 | 19 | C().test(); 20 | -------------------------------------------------------------------------------- /chapter13/tests/test-inheritance5.lox.expected: -------------------------------------------------------------------------------- 1 | Method A 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-inheritance6.lox: -------------------------------------------------------------------------------- 1 | class Eclair { 2 | cook() { 3 | super.cook(); 4 | print "Pipe full of crème pâtissière."; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /chapter13/tests/test-inheritance6.lox.expected: -------------------------------------------------------------------------------- 1 | [line 3] Error at 'super': Can't user 'super' in a class with no superclass. 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-inheritance7.lox: -------------------------------------------------------------------------------- 1 | super.notEvenInAClass(); 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-inheritance7.lox.expected: -------------------------------------------------------------------------------- 1 | [line 1] Error at 'super': Can't user 'super' outside of a class. 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-resolving.lox: -------------------------------------------------------------------------------- 1 | var a = "global"; 2 | { 3 | fun showA() { 4 | print a; 5 | } 6 | 7 | showA(); 8 | var a = "block"; 9 | showA(); 10 | } 11 | -------------------------------------------------------------------------------- /chapter13/tests/test-resolving.lox.expected: -------------------------------------------------------------------------------- 1 | global 2 | global 3 | -------------------------------------------------------------------------------- /chapter13/tests/test-resolving2.lox: -------------------------------------------------------------------------------- 1 | var a = "outer"; 2 | { 3 | var a = a; 4 | } 5 | -------------------------------------------------------------------------------- /chapter13/tests/test-resolving2.lox.expected: -------------------------------------------------------------------------------- 1 | [line 3] Error at 'a': Can't read local variable in its own initializer. 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-resolving3.lox: -------------------------------------------------------------------------------- 1 | fun bad() { 2 | var a = "first"; 3 | var a = "second"; 4 | } 5 | -------------------------------------------------------------------------------- /chapter13/tests/test-resolving3.lox.expected: -------------------------------------------------------------------------------- 1 | [line 3] Error at 'a': Already a variable with this name in this scope. 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-resolving4.lox: -------------------------------------------------------------------------------- 1 | return "at top level"; 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-resolving4.lox.expected: -------------------------------------------------------------------------------- 1 | [line 1] Error at 'return': Can't return from top-level code. 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-statements.lox: -------------------------------------------------------------------------------- 1 | print "one"; 2 | print true; 3 | print 2 + 1; 4 | -------------------------------------------------------------------------------- /chapter13/tests/test-statements.lox.expected: -------------------------------------------------------------------------------- 1 | one 2 | true 3 | 3.000000 4 | -------------------------------------------------------------------------------- /chapter13/tests/test-statements2.lox: -------------------------------------------------------------------------------- 1 | var a = "before"; 2 | print a; // "before". 3 | var a = "after"; 4 | print a; // "after". 5 | -------------------------------------------------------------------------------- /chapter13/tests/test-statements2.lox.expected: -------------------------------------------------------------------------------- 1 | before 2 | after 3 | -------------------------------------------------------------------------------- /chapter13/tests/test-statements3.lox: -------------------------------------------------------------------------------- 1 | var a; 2 | print a; // "nil". 3 | -------------------------------------------------------------------------------- /chapter13/tests/test-statements3.lox.expected: -------------------------------------------------------------------------------- 1 | nil 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-statements4.lox: -------------------------------------------------------------------------------- 1 | var a = 1; 2 | var b = 2; 3 | print a + b; 4 | -------------------------------------------------------------------------------- /chapter13/tests/test-statements4.lox.expected: -------------------------------------------------------------------------------- 1 | 3.000000 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-statements5.lox: -------------------------------------------------------------------------------- 1 | var a = 1; 2 | print a = 2; // "2". 3 | -------------------------------------------------------------------------------- /chapter13/tests/test-statements5.lox.expected: -------------------------------------------------------------------------------- 1 | 2.000000 2 | -------------------------------------------------------------------------------- /chapter13/tests/test-statements6.lox: -------------------------------------------------------------------------------- 1 | var a = "global a"; 2 | var b = "global b"; 3 | var c = "global c"; 4 | { 5 | var a = "outer a"; 6 | var b = "outer b"; 7 | { 8 | var a = "inner a"; 9 | print a; 10 | print b; 11 | print c; 12 | } 13 | print a; 14 | print b; 15 | print c; 16 | } 17 | print a; 18 | print b; 19 | print c; 20 | -------------------------------------------------------------------------------- /chapter13/tests/test-statements6.lox.expected: -------------------------------------------------------------------------------- 1 | inner a 2 | outer b 3 | global c 4 | outer a 5 | outer b 6 | global c 7 | global a 8 | global b 9 | global c 10 | -------------------------------------------------------------------------------- /chapter4/Error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | inline bool hadError = false; 7 | 8 | static void report(int line, std::string_view where, 9 | std::string_view message) { 10 | std::cerr << 11 | "[line " << line << "] Error" << where << ": " << message << 12 | "\n"; 13 | hadError = true; 14 | } 15 | 16 | void error(int line, std::string_view message) { 17 | report(line, "", message); 18 | } 19 | -------------------------------------------------------------------------------- /chapter4/Lox.cpp: -------------------------------------------------------------------------------- 1 | #include // std::strerror 2 | #include // readFile 3 | #include // std::getline 4 | #include 5 | #include 6 | #include "Error.h" 7 | #include "Scanner.h" 8 | 9 | std::string readFile(std::string_view path) { 10 | std::ifstream file{path.data(), std::ios::in | std::ios::binary | 11 | std::ios::ate}; 12 | if (!file) { 13 | std::cerr << "Failed to open file " << path << ": " 14 | << std::strerror(errno) << "\n"; 15 | std::exit(74); 16 | }; 17 | 18 | std::string contents; 19 | contents.resize(file.tellg()); 20 | 21 | file.seekg(0, std::ios::beg); 22 | file.read(contents.data(), contents.size()); 23 | 24 | return contents; 25 | } 26 | 27 | void run(std::string_view source) { 28 | Scanner scanner {source}; 29 | std::vector tokens = scanner.scanTokens(); 30 | 31 | // For now, just print the tokens. 32 | for (const Token& token : tokens) { 33 | std::cout << token.toString() << "\n"; 34 | } 35 | } 36 | 37 | void runFile(std::string_view path) { 38 | std::string contents = readFile(path); 39 | run(contents); 40 | 41 | // Indicate an error in the exit code. 42 | if (hadError) std::exit(65); 43 | } 44 | 45 | void runPrompt() { 46 | for (;;) { 47 | std::cout << "> "; 48 | std::string line; 49 | if (!std::getline(std::cin, line)) break; 50 | run(line); 51 | hadError = false; 52 | } 53 | } 54 | 55 | int main(int argc, char* argv[]) { 56 | if (argc > 2) { 57 | std::cout << "Usage: jlox [script]\n"; 58 | std::exit(64); 59 | } else if (argc == 2) { 60 | runFile(argv[1]); 61 | } else { 62 | runPrompt(); 63 | } 64 | } -------------------------------------------------------------------------------- /chapter4/Makefile: -------------------------------------------------------------------------------- 1 | CXX := g++ 2 | CXXFLAGS := -ggdb -std=c++17 3 | CPPFLAGS := -MMD 4 | 5 | COMPILE := $(CXX) $(CXXFLAGS) $(CPPFLAGS) 6 | 7 | SRCS := Lox.cpp 8 | DEPS := $(SRCS:.cpp=.d) 9 | 10 | 11 | jlox: Lox.o 12 | @$(COMPILE) $< -o $@ 13 | 14 | 15 | .PHONY: clean 16 | clean: 17 | rm -f *.d *.o jlox 18 | 19 | 20 | -include $(DEPS) 21 | 22 | 23 | .PHONY: test-lexing 24 | test-lexing: 25 | @make jlox >/dev/null 26 | @echo "testing jlox with test-lexing.lox ..." 27 | @./jlox tests/test-lexing.lox | diff -u --color tests/test-lexing.lox.expected -; 28 | 29 | 30 | .PHONY: test-lexing2 31 | test-lexing2: 32 | @make jlox >/dev/null 33 | @echo "testing jlox with test-lexing2.lox ..." 34 | @./jlox tests/test-lexing2.lox | diff -u --color tests/test-lexing2.lox.expected -; 35 | -------------------------------------------------------------------------------- /chapter4/Token.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include // std::move 6 | #include "TokenType.h" 7 | 8 | class Token { 9 | public: 10 | const TokenType type; 11 | const std::string lexeme; 12 | const std::any literal; 13 | const int line; 14 | 15 | Token(TokenType type, std::string lexeme, std::any literal, 16 | int line) 17 | : type{type}, lexeme{std::move(lexeme)}, 18 | literal{std::move(literal)}, line{line} 19 | {} 20 | 21 | std::string toString() const { 22 | std::string literal_text; 23 | 24 | switch (type) { 25 | case (IDENTIFIER): 26 | literal_text = lexeme; 27 | break; 28 | case (STRING): 29 | literal_text = std::any_cast(literal); 30 | break; 31 | case (NUMBER): 32 | literal_text = std::to_string(std::any_cast(literal)); 33 | break; 34 | case (TRUE): 35 | literal_text = "true"; 36 | break; 37 | case (FALSE): 38 | literal_text = "false"; 39 | break; 40 | default: 41 | literal_text = "nil"; 42 | } 43 | 44 | return ::toString(type) + " " + lexeme + " " + literal_text; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /chapter4/TokenType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | enum TokenType { 6 | // Single-character tokens. 7 | LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, 8 | COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, 9 | 10 | // One or two character tokens. 11 | BANG, BANG_EQUAL, 12 | EQUAL, EQUAL_EQUAL, 13 | GREATER, GREATER_EQUAL, 14 | LESS, LESS_EQUAL, 15 | 16 | // Literals. 17 | IDENTIFIER, STRING, NUMBER, 18 | 19 | // Keywords. 20 | AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR, 21 | PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE, 22 | 23 | END_OF_FILE 24 | }; 25 | 26 | std::string toString(TokenType type) { 27 | static const std::string strings[] = { 28 | "LEFT_PAREN", "RIGHT_PAREN", "LEFT_BRACE", "RIGHT_BRACE", 29 | "COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON", "SLASH", "STAR", 30 | "BANG", "BANG_EQUAL", 31 | "EQUAL", "EQUAL_EQUAL", 32 | "GREATER", "GREATER_EQUAL", 33 | "LESS", "LESS_EQUAL", 34 | "IDENTIFIER", "STRING", "NUMBER", 35 | "AND", "CLASS", "ELSE", "FALSE", "FUN", "FOR", "IF", "NIL", "OR", 36 | "PRINT", "RETURN", "SUPER", "THIS", "TRUE", "VAR", "WHILE", 37 | "END_OF_FILE" 38 | }; 39 | 40 | return strings[static_cast(type)]; 41 | } 42 | -------------------------------------------------------------------------------- /chapter4/tests/test-lexing.lox: -------------------------------------------------------------------------------- 1 | // this is a comment 2 | (( )){} // grouping stuff 3 | !*+-/=<> <= == // operators 4 | -------------------------------------------------------------------------------- /chapter4/tests/test-lexing.lox.expected: -------------------------------------------------------------------------------- 1 | LEFT_PAREN ( nil 2 | LEFT_PAREN ( nil 3 | RIGHT_PAREN ) nil 4 | RIGHT_PAREN ) nil 5 | LEFT_BRACE { nil 6 | RIGHT_BRACE } nil 7 | BANG ! nil 8 | STAR * nil 9 | PLUS + nil 10 | MINUS - nil 11 | SLASH / nil 12 | EQUAL = nil 13 | LESS < nil 14 | GREATER > nil 15 | LESS_EQUAL <= nil 16 | EQUAL_EQUAL == nil 17 | END_OF_FILE nil 18 | -------------------------------------------------------------------------------- /chapter4/tests/test-lexing2.lox: -------------------------------------------------------------------------------- 1 | <>{},.-+;/* 2 | ! != = == > >= < <= 3 | a "a" 1 4 | and class else false fun for if nil or print return super this true var while 5 | -------------------------------------------------------------------------------- /chapter4/tests/test-lexing2.lox.expected: -------------------------------------------------------------------------------- 1 | LESS < nil 2 | GREATER > nil 3 | LEFT_BRACE { nil 4 | RIGHT_BRACE } nil 5 | COMMA , nil 6 | DOT . nil 7 | MINUS - nil 8 | PLUS + nil 9 | SEMICOLON ; nil 10 | SLASH / nil 11 | STAR * nil 12 | BANG ! nil 13 | BANG_EQUAL != nil 14 | EQUAL = nil 15 | EQUAL_EQUAL == nil 16 | GREATER > nil 17 | GREATER_EQUAL >= nil 18 | LESS < nil 19 | LESS_EQUAL <= nil 20 | IDENTIFIER a a 21 | STRING "a" a 22 | NUMBER 1 1.000000 23 | AND and nil 24 | CLASS class nil 25 | ELSE else nil 26 | FALSE false false 27 | FUN fun nil 28 | FOR for nil 29 | IF if nil 30 | NIL nil nil 31 | OR or nil 32 | PRINT print nil 33 | RETURN return nil 34 | SUPER super nil 35 | THIS this nil 36 | TRUE true true 37 | VAR var nil 38 | WHILE while nil 39 | END_OF_FILE nil 40 | -------------------------------------------------------------------------------- /chapter5/AstPrinter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "Expr.h" 11 | 12 | class AstPrinter: public ExprVisitor { 13 | public: 14 | std::string print(std::shared_ptr expr) { 15 | return std::any_cast(expr->accept(*this)); 16 | } 17 | 18 | std::any visitBinaryExpr(std::shared_ptr expr) override { 19 | return parenthesize(expr->op.lexeme, 20 | expr->left, expr->right); 21 | } 22 | 23 | std::any visitGroupingExpr( 24 | std::shared_ptr expr) override { 25 | return parenthesize("group", expr->expression); 26 | } 27 | 28 | std::any visitLiteralExpr(std::shared_ptr expr) override { 29 | auto& value_type = expr->value.type(); 30 | 31 | if (value_type == typeid(nullptr)) { 32 | return "nil"; 33 | } else if (value_type == typeid(std::string)) { 34 | return std::any_cast(expr->value); 35 | } else if (value_type == typeid(double)) { 36 | return std::to_string(std::any_cast(expr->value)); 37 | } else if (value_type == typeid(bool)) { 38 | return std::any_cast(expr->value) ? "true" : "false"; 39 | } 40 | 41 | return "Error in visitLiteralExpr: literal type not recognized."; 42 | } 43 | 44 | std::any visitUnaryExpr(std::shared_ptr expr) override { 45 | return parenthesize(expr->op.lexeme, expr->right); 46 | } 47 | 48 | private: 49 | template 50 | std::string parenthesize(std::string_view name, E... expr) 51 | { 52 | assert((... && std::is_same_v>)); 53 | 54 | std::ostringstream builder; 55 | 56 | builder << "(" << name; 57 | ((builder << " " << print(expr)), ...); 58 | builder << ")"; 59 | 60 | return builder.str(); 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /chapter5/AstPrinterDriver.cpp: -------------------------------------------------------------------------------- 1 | #include "AstPrinter.h" 2 | 3 | int main(int argc, char* argv[]) { 4 | std::shared_ptr expression = std::make_shared( 5 | std::make_shared( 6 | Token{MINUS, "-", nullptr, 1}, 7 | std::make_shared(123.) 8 | ), 9 | Token{STAR, "*", nullptr, 1}, 10 | std::make_shared( 11 | std::make_shared(45.67))); 12 | 13 | std::cout << AstPrinter{}.print(expression) << "\n"; 14 | } -------------------------------------------------------------------------------- /chapter5/Error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "Token.h" 6 | 7 | inline bool hadError = false; 8 | 9 | static void report(int line, std::string_view where, 10 | std::string_view message) { 11 | std::cerr << 12 | "[line " << line << "] Error" << where << ": " << message << 13 | "\n"; 14 | hadError = true; 15 | } 16 | 17 | void error(int line, std::string_view message) { 18 | report(line, "", message); 19 | } 20 | -------------------------------------------------------------------------------- /chapter5/Expr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include // std::move 6 | #include 7 | #include "Token.h" 8 | 9 | struct Binary; 10 | struct Grouping; 11 | struct Literal; 12 | struct Unary; 13 | 14 | struct ExprVisitor { 15 | virtual std::any visitBinaryExpr(std::shared_ptr expr) = 0; 16 | virtual std::any visitGroupingExpr(std::shared_ptr expr) = 0; 17 | virtual std::any visitLiteralExpr(std::shared_ptr expr) = 0; 18 | virtual std::any visitUnaryExpr(std::shared_ptr expr) = 0; 19 | virtual ~ExprVisitor() = default; 20 | }; 21 | 22 | struct Expr { 23 | virtual std::any accept(ExprVisitor& visitor) = 0; 24 | }; 25 | 26 | struct Binary: Expr, public std::enable_shared_from_this { 27 | Binary(std::shared_ptr left, Token op, std::shared_ptr right) 28 | : left{std::move(left)}, op{std::move(op)}, right{std::move(right)} 29 | {} 30 | 31 | std::any accept(ExprVisitor& visitor)override { 32 | return visitor.visitBinaryExpr(shared_from_this()); 33 | } 34 | 35 | const std::shared_ptr left; 36 | const Token op; 37 | const std::shared_ptr right; 38 | }; 39 | 40 | struct Grouping: Expr, public std::enable_shared_from_this { 41 | Grouping(std::shared_ptr expression) 42 | : expression{std::move(expression)} 43 | {} 44 | 45 | std::any accept(ExprVisitor& visitor)override { 46 | return visitor.visitGroupingExpr(shared_from_this()); 47 | } 48 | 49 | const std::shared_ptr expression; 50 | }; 51 | 52 | struct Literal: Expr, public std::enable_shared_from_this { 53 | Literal(std::any value) 54 | : value{std::move(value)} 55 | {} 56 | 57 | std::any accept(ExprVisitor& visitor)override { 58 | return visitor.visitLiteralExpr(shared_from_this()); 59 | } 60 | 61 | const std::any value; 62 | }; 63 | 64 | struct Unary: Expr, public std::enable_shared_from_this { 65 | Unary(Token op, std::shared_ptr right) 66 | : op{std::move(op)}, right{std::move(right)} 67 | {} 68 | 69 | std::any accept(ExprVisitor& visitor)override { 70 | return visitor.visitUnaryExpr(shared_from_this()); 71 | } 72 | 73 | const Token op; 74 | const std::shared_ptr right; 75 | }; 76 | 77 | -------------------------------------------------------------------------------- /chapter5/Lox.cpp: -------------------------------------------------------------------------------- 1 | #include // std::strerror 2 | #include // readFile 3 | #include // std::getline 4 | #include 5 | #include 6 | #include "Error.h" 7 | #include "Scanner.h" 8 | 9 | std::string readFile(std::string_view path) { 10 | std::ifstream file{path.data(), std::ios::in | std::ios::binary | 11 | std::ios::ate}; 12 | if (!file) { 13 | std::cerr << "Failed to open file " << path << ": " 14 | << std::strerror(errno) << "\n"; 15 | std::exit(74); 16 | }; 17 | 18 | std::string contents; 19 | contents.resize(file.tellg()); 20 | 21 | file.seekg(0, std::ios::beg); 22 | file.read(contents.data(), contents.size()); 23 | 24 | return contents; 25 | } 26 | 27 | void run(std::string_view source) { 28 | Scanner scanner {source}; 29 | std::vector tokens = scanner.scanTokens(); 30 | 31 | // For now, just print the tokens. 32 | for (const Token& token : tokens) { 33 | std::cout << token.toString() << "\n"; 34 | } 35 | } 36 | 37 | void runFile(std::string_view path) { 38 | std::string contents = readFile(path); 39 | run(contents); 40 | 41 | // Indicate an error in the exit code. 42 | if (hadError) std::exit(65); 43 | } 44 | 45 | void runPrompt() { 46 | for (;;) { 47 | std::cout << "> "; 48 | std::string line; 49 | if (!std::getline(std::cin, line)) break; 50 | run(line); 51 | hadError = false; 52 | } 53 | } 54 | 55 | int main(int argc, char* argv[]) { 56 | if (argc > 2) { 57 | std::cout << "Usage: jlox [script]\n"; 58 | std::exit(64); 59 | } else if (argc == 2) { 60 | runFile(argv[1]); 61 | } else { 62 | runPrompt(); 63 | } 64 | } -------------------------------------------------------------------------------- /chapter5/Makefile: -------------------------------------------------------------------------------- 1 | CXX := g++ 2 | CXXFLAGS := -ggdb -std=c++17 3 | CPPFLAGS := -MMD 4 | 5 | COMPILE := $(CXX) $(CXXFLAGS) $(CPPFLAGS) 6 | 7 | SRCS := AstPrinterDriver.cpp GenerateAst.cpp Lox.cpp 8 | DEPS := $(SRCS:.cpp=.d) 9 | 10 | 11 | jlox: Lox.o 12 | @$(COMPILE) $< -o $@ 13 | 14 | 15 | ast_printer: Expr.h AstPrinterDriver.o 16 | @$(COMPILE) AstPrinterDriver.o -o $@ 17 | 18 | 19 | generate_ast: GenerateAst.o 20 | @$(COMPILE) $< -o $@ 21 | 22 | 23 | Expr.h: generate_ast 24 | @./generate_ast . 25 | 26 | 27 | .PHONY: clean 28 | clean: 29 | rm -f *.d *.o ast_printer generate_ast jlox 30 | 31 | 32 | -include $(DEPS) 33 | 34 | 35 | .PHONY: test-lexing 36 | test-lexing: 37 | @make jlox >/dev/null 38 | @echo "testing jlox with test-lexing.lox ..." 39 | @./jlox tests/test-lexing.lox | diff -u --color tests/test-lexing.lox.expected -; 40 | 41 | 42 | .PHONY: test-lexing2 43 | test-lexing2: 44 | @make jlox >/dev/null 45 | @echo "testing jlox with test-lexing2.lox ..." 46 | @./jlox tests/test-lexing2.lox | diff -u --color tests/test-lexing2.lox.expected -; 47 | -------------------------------------------------------------------------------- /chapter5/Token.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include // std::move 6 | #include "TokenType.h" 7 | 8 | class Token { 9 | public: 10 | const TokenType type; 11 | const std::string lexeme; 12 | const std::any literal; 13 | const int line; 14 | 15 | Token(TokenType type, std::string lexeme, std::any literal, 16 | int line) 17 | : type{type}, lexeme{std::move(lexeme)}, 18 | literal{std::move(literal)}, line{line} 19 | {} 20 | 21 | std::string toString() const { 22 | std::string literal_text; 23 | 24 | switch (type) { 25 | case (IDENTIFIER): 26 | literal_text = lexeme; 27 | break; 28 | case (STRING): 29 | literal_text = std::any_cast(literal); 30 | break; 31 | case (NUMBER): 32 | literal_text = std::to_string(std::any_cast(literal)); 33 | break; 34 | case (TRUE): 35 | literal_text = "true"; 36 | break; 37 | case (FALSE): 38 | literal_text = "false"; 39 | break; 40 | default: 41 | literal_text = "nil"; 42 | } 43 | 44 | return ::toString(type) + " " + lexeme + " " + literal_text; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /chapter5/TokenType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | enum TokenType { 6 | // Single-character tokens. 7 | LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, 8 | COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, 9 | 10 | // One or two character tokens. 11 | BANG, BANG_EQUAL, 12 | EQUAL, EQUAL_EQUAL, 13 | GREATER, GREATER_EQUAL, 14 | LESS, LESS_EQUAL, 15 | 16 | // Literals. 17 | IDENTIFIER, STRING, NUMBER, 18 | 19 | // Keywords. 20 | AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR, 21 | PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE, 22 | 23 | END_OF_FILE 24 | }; 25 | 26 | std::string toString(TokenType type) { 27 | static const std::string strings[] = { 28 | "LEFT_PAREN", "RIGHT_PAREN", "LEFT_BRACE", "RIGHT_BRACE", 29 | "COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON", "SLASH", "STAR", 30 | "BANG", "BANG_EQUAL", 31 | "EQUAL", "EQUAL_EQUAL", 32 | "GREATER", "GREATER_EQUAL", 33 | "LESS", "LESS_EQUAL", 34 | "IDENTIFIER", "STRING", "NUMBER", 35 | "AND", "CLASS", "ELSE", "FALSE", "FUN", "FOR", "IF", "NIL", "OR", 36 | "PRINT", "RETURN", "SUPER", "THIS", "TRUE", "VAR", "WHILE", 37 | "END_OF_FILE" 38 | }; 39 | 40 | return strings[static_cast(type)]; 41 | } 42 | -------------------------------------------------------------------------------- /chapter5/tests/test-lexing.lox: -------------------------------------------------------------------------------- 1 | // this is a comment 2 | (( )){} // grouping stuff 3 | !*+-/=<> <= == // operators 4 | -------------------------------------------------------------------------------- /chapter5/tests/test-lexing.lox.expected: -------------------------------------------------------------------------------- 1 | LEFT_PAREN ( nil 2 | LEFT_PAREN ( nil 3 | RIGHT_PAREN ) nil 4 | RIGHT_PAREN ) nil 5 | LEFT_BRACE { nil 6 | RIGHT_BRACE } nil 7 | BANG ! nil 8 | STAR * nil 9 | PLUS + nil 10 | MINUS - nil 11 | SLASH / nil 12 | EQUAL = nil 13 | LESS < nil 14 | GREATER > nil 15 | LESS_EQUAL <= nil 16 | EQUAL_EQUAL == nil 17 | END_OF_FILE nil 18 | -------------------------------------------------------------------------------- /chapter5/tests/test-lexing2.lox: -------------------------------------------------------------------------------- 1 | <>{},.-+;/* 2 | ! != = == > >= < <= 3 | a "a" 1 4 | and class else false fun for if nil or print return super this true var while 5 | -------------------------------------------------------------------------------- /chapter5/tests/test-lexing2.lox.expected: -------------------------------------------------------------------------------- 1 | LESS < nil 2 | GREATER > nil 3 | LEFT_BRACE { nil 4 | RIGHT_BRACE } nil 5 | COMMA , nil 6 | DOT . nil 7 | MINUS - nil 8 | PLUS + nil 9 | SEMICOLON ; nil 10 | SLASH / nil 11 | STAR * nil 12 | BANG ! nil 13 | BANG_EQUAL != nil 14 | EQUAL = nil 15 | EQUAL_EQUAL == nil 16 | GREATER > nil 17 | GREATER_EQUAL >= nil 18 | LESS < nil 19 | LESS_EQUAL <= nil 20 | IDENTIFIER a a 21 | STRING "a" a 22 | NUMBER 1 1.000000 23 | AND and nil 24 | CLASS class nil 25 | ELSE else nil 26 | FALSE false false 27 | FUN fun nil 28 | FOR for nil 29 | IF if nil 30 | NIL nil nil 31 | OR or nil 32 | PRINT print nil 33 | RETURN return nil 34 | SUPER super nil 35 | THIS this nil 36 | TRUE true true 37 | VAR var nil 38 | WHILE while nil 39 | END_OF_FILE nil 40 | -------------------------------------------------------------------------------- /chapter6/AstPrinter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "Expr.h" 11 | 12 | class AstPrinter: public ExprVisitor { 13 | public: 14 | std::string print(std::shared_ptr expr) { 15 | return std::any_cast(expr->accept(*this)); 16 | } 17 | 18 | std::any visitBinaryExpr(std::shared_ptr expr) override { 19 | return parenthesize(expr->op.lexeme, 20 | expr->left, expr->right); 21 | } 22 | 23 | std::any visitGroupingExpr( 24 | std::shared_ptr expr) override { 25 | return parenthesize("group", expr->expression); 26 | } 27 | 28 | std::any visitLiteralExpr(std::shared_ptr expr) override { 29 | auto& value_type = expr->value.type(); 30 | 31 | if (value_type == typeid(nullptr)) { 32 | return "nil"; 33 | } else if (value_type == typeid(std::string)) { 34 | return std::any_cast(expr->value); 35 | } else if (value_type == typeid(double)) { 36 | return std::to_string(std::any_cast(expr->value)); 37 | } else if (value_type == typeid(bool)) { 38 | return std::any_cast(expr->value) ? "true" : "false"; 39 | } 40 | 41 | return "Error in visitLiteralExpr: literal type not recognized."; 42 | } 43 | 44 | std::any visitUnaryExpr(std::shared_ptr expr) override { 45 | return parenthesize(expr->op.lexeme, expr->right); 46 | } 47 | 48 | private: 49 | template 50 | std::string parenthesize(std::string_view name, E... expr) 51 | { 52 | assert((... && std::is_same_v>)); 53 | 54 | std::ostringstream builder; 55 | 56 | builder << "(" << name; 57 | ((builder << " " << print(expr)), ...); 58 | builder << ")"; 59 | 60 | return builder.str(); 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /chapter6/AstPrinterDriver.cpp: -------------------------------------------------------------------------------- 1 | #include "AstPrinter.h" 2 | 3 | int main(int argc, char* argv[]) { 4 | std::shared_ptr expression = std::make_shared( 5 | std::make_shared( 6 | Token{MINUS, "-", nullptr, 1}, 7 | std::make_shared(123.) 8 | ), 9 | Token{STAR, "*", nullptr, 1}, 10 | std::make_shared( 11 | std::make_shared(45.67))); 12 | 13 | std::cout << AstPrinter{}.print(expression) << "\n"; 14 | } -------------------------------------------------------------------------------- /chapter6/Error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "Token.h" 6 | 7 | inline bool hadError = false; 8 | 9 | static void report(int line, std::string_view where, 10 | std::string_view message) { 11 | std::cerr << 12 | "[line " << line << "] Error" << where << ": " << message << 13 | "\n"; 14 | hadError = true; 15 | } 16 | 17 | void error(const Token& token, std::string_view message) { 18 | if (token.type == END_OF_FILE) { 19 | report(token.line, " at end", message); 20 | } else { 21 | report(token.line, " at '" + token.lexeme + "'", message); 22 | } 23 | } 24 | 25 | void error(int line, std::string_view message) { 26 | report(line, "", message); 27 | } 28 | -------------------------------------------------------------------------------- /chapter6/Expr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include // std::move 6 | #include 7 | #include "Token.h" 8 | 9 | struct Binary; 10 | struct Grouping; 11 | struct Literal; 12 | struct Unary; 13 | 14 | struct ExprVisitor { 15 | virtual std::any visitBinaryExpr(std::shared_ptr expr) = 0; 16 | virtual std::any visitGroupingExpr(std::shared_ptr expr) = 0; 17 | virtual std::any visitLiteralExpr(std::shared_ptr expr) = 0; 18 | virtual std::any visitUnaryExpr(std::shared_ptr expr) = 0; 19 | virtual ~ExprVisitor() = default; 20 | }; 21 | 22 | struct Expr { 23 | virtual std::any accept(ExprVisitor& visitor) = 0; 24 | }; 25 | 26 | struct Binary: Expr, public std::enable_shared_from_this { 27 | Binary(std::shared_ptr left, Token op, std::shared_ptr right) 28 | : left{std::move(left)}, op{std::move(op)}, right{std::move(right)} 29 | {} 30 | 31 | std::any accept(ExprVisitor& visitor) override { 32 | return visitor.visitBinaryExpr(shared_from_this()); 33 | } 34 | 35 | const std::shared_ptr left; 36 | const Token op; 37 | const std::shared_ptr right; 38 | }; 39 | 40 | struct Grouping: Expr, public std::enable_shared_from_this { 41 | Grouping(std::shared_ptr expression) 42 | : expression{std::move(expression)} 43 | {} 44 | 45 | std::any accept(ExprVisitor& visitor) override { 46 | return visitor.visitGroupingExpr(shared_from_this()); 47 | } 48 | 49 | const std::shared_ptr expression; 50 | }; 51 | 52 | struct Literal: Expr, public std::enable_shared_from_this { 53 | Literal(std::any value) 54 | : value{std::move(value)} 55 | {} 56 | 57 | std::any accept(ExprVisitor& visitor) override { 58 | return visitor.visitLiteralExpr(shared_from_this()); 59 | } 60 | 61 | const std::any value; 62 | }; 63 | 64 | struct Unary: Expr, public std::enable_shared_from_this { 65 | Unary(Token op, std::shared_ptr right) 66 | : op{std::move(op)}, right{std::move(right)} 67 | {} 68 | 69 | std::any accept(ExprVisitor& visitor) override { 70 | return visitor.visitUnaryExpr(shared_from_this()); 71 | } 72 | 73 | const Token op; 74 | const std::shared_ptr right; 75 | }; 76 | 77 | -------------------------------------------------------------------------------- /chapter6/Lox.cpp: -------------------------------------------------------------------------------- 1 | #include // std::strerror 2 | #include // readFile 3 | #include // std::getline 4 | #include 5 | #include 6 | #include "AstPrinter.h" 7 | #include "Error.h" 8 | #include "Parser.h" 9 | #include "Scanner.h" 10 | 11 | std::string readFile(std::string_view path) { 12 | std::ifstream file{path.data(), std::ios::in | std::ios::binary | 13 | std::ios::ate}; 14 | if (!file) { 15 | std::cerr << "Failed to open file " << path << ": " 16 | << std::strerror(errno) << "\n"; 17 | std::exit(74); 18 | }; 19 | 20 | std::string contents; 21 | contents.resize(file.tellg()); 22 | 23 | file.seekg(0, std::ios::beg); 24 | file.read(contents.data(), contents.size()); 25 | 26 | return contents; 27 | } 28 | 29 | void run(std::string_view source) { 30 | Scanner scanner {source}; 31 | std::vector tokens = scanner.scanTokens(); 32 | Parser parser{tokens}; 33 | std::shared_ptr expression = parser.parse(); 34 | 35 | // Stop if there was a syntax error. 36 | if (hadError) return; 37 | 38 | std::cout << AstPrinter{}.print(expression) << "\n"; 39 | } 40 | 41 | void runFile(std::string_view path) { 42 | std::string contents = readFile(path); 43 | run(contents); 44 | 45 | // Indicate an error in the exit code. 46 | if (hadError) std::exit(65); 47 | } 48 | 49 | void runPrompt() { 50 | for (;;) { 51 | std::cout << "> "; 52 | std::string line; 53 | if (!std::getline(std::cin, line)) break; 54 | run(line); 55 | hadError = false; 56 | } 57 | } 58 | 59 | int main(int argc, char* argv[]) { 60 | if (argc > 2) { 61 | std::cout << "Usage: jlox [script]\n"; 62 | std::exit(64); 63 | } else if (argc == 2) { 64 | runFile(argv[1]); 65 | } else { 66 | runPrompt(); 67 | } 68 | } -------------------------------------------------------------------------------- /chapter6/Makefile: -------------------------------------------------------------------------------- 1 | CXX := g++ 2 | CXXFLAGS := -ggdb -std=c++17 3 | CPPFLAGS := -MMD 4 | 5 | COMPILE := $(CXX) $(CXXFLAGS) $(CPPFLAGS) 6 | 7 | SRCS := AstPrinterDriver.cpp GenerateAst.cpp Lox.cpp 8 | DEPS := $(SRCS:.cpp=.d) 9 | 10 | 11 | jlox: ast_printer Lox.o 12 | @$(COMPILE) Lox.o -o $@ 13 | 14 | 15 | ast_printer: Expr.h AstPrinterDriver.o 16 | @$(COMPILE) AstPrinterDriver.o -o $@ 17 | 18 | 19 | generate_ast: GenerateAst.o 20 | @$(COMPILE) $< -o $@ 21 | 22 | 23 | Expr.h: generate_ast 24 | @./generate_ast . 25 | 26 | 27 | .PHONY: clean 28 | clean: 29 | rm -f *.d *.o ast_printer generate_ast jlox 30 | 31 | 32 | -include $(DEPS) 33 | 34 | 35 | .PHONY: test-parsing 36 | test-parsing: 37 | @make jlox >/dev/null 38 | @echo "testing jlox with test-parsing.lox ..." 39 | @./jlox tests/test-parsing.lox | diff -u --color tests/test-parsing.lox.expected -; 40 | -------------------------------------------------------------------------------- /chapter6/Token.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include // std::move 6 | #include "TokenType.h" 7 | 8 | class Token { 9 | public: 10 | const TokenType type; 11 | const std::string lexeme; 12 | const std::any literal; 13 | const int line; 14 | 15 | Token(TokenType type, std::string lexeme, std::any literal, 16 | int line) 17 | : type{type}, lexeme{std::move(lexeme)}, 18 | literal{std::move(literal)}, line{line} 19 | {} 20 | 21 | std::string toString() const { 22 | std::string literal_text; 23 | 24 | switch (type) { 25 | case (IDENTIFIER): 26 | literal_text = lexeme; 27 | break; 28 | case (STRING): 29 | literal_text = std::any_cast(literal); 30 | break; 31 | case (NUMBER): 32 | literal_text = std::to_string(std::any_cast(literal)); 33 | break; 34 | case (TRUE): 35 | literal_text = "true"; 36 | break; 37 | case (FALSE): 38 | literal_text = "false"; 39 | break; 40 | default: 41 | literal_text = "nil"; 42 | } 43 | 44 | return ::toString(type) + " " + lexeme + " " + literal_text; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /chapter6/TokenType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | enum TokenType { 6 | // Single-character tokens. 7 | LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, 8 | COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, 9 | 10 | // One or two character tokens. 11 | BANG, BANG_EQUAL, 12 | EQUAL, EQUAL_EQUAL, 13 | GREATER, GREATER_EQUAL, 14 | LESS, LESS_EQUAL, 15 | 16 | // Literals. 17 | IDENTIFIER, STRING, NUMBER, 18 | 19 | // Keywords. 20 | AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR, 21 | PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE, 22 | 23 | END_OF_FILE 24 | }; 25 | 26 | std::string toString(TokenType type) { 27 | static const std::string strings[] = { 28 | "LEFT_PAREN", "RIGHT_PAREN", "LEFT_BRACE", "RIGHT_BRACE", 29 | "COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON", "SLASH", "STAR", 30 | "BANG", "BANG_EQUAL", 31 | "EQUAL", "EQUAL_EQUAL", 32 | "GREATER", "GREATER_EQUAL", 33 | "LESS", "LESS_EQUAL", 34 | "IDENTIFIER", "STRING", "NUMBER", 35 | "AND", "CLASS", "ELSE", "FALSE", "FUN", "FOR", "IF", "NIL", "OR", 36 | "PRINT", "RETURN", "SUPER", "THIS", "TRUE", "VAR", "WHILE", 37 | "END_OF_FILE" 38 | }; 39 | 40 | return strings[static_cast(type)]; 41 | } 42 | -------------------------------------------------------------------------------- /chapter6/tests/test-parsing.lox: -------------------------------------------------------------------------------- 1 | -123 * (45.67) 2 | -------------------------------------------------------------------------------- /chapter6/tests/test-parsing.lox.expected: -------------------------------------------------------------------------------- 1 | (* (- 123.000000) (group 45.670000)) 2 | -------------------------------------------------------------------------------- /chapter7/AstPrinter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "Expr.h" 11 | 12 | class AstPrinter: public ExprVisitor { 13 | public: 14 | std::string print(std::shared_ptr expr) { 15 | return std::any_cast(expr->accept(*this)); 16 | } 17 | 18 | std::any visitBinaryExpr(std::shared_ptr expr) override { 19 | return parenthesize(expr->op.lexeme, 20 | expr->left, expr->right); 21 | } 22 | 23 | std::any visitGroupingExpr( 24 | std::shared_ptr expr) override { 25 | return parenthesize("group", expr->expression); 26 | } 27 | 28 | std::any visitLiteralExpr(std::shared_ptr expr) override { 29 | auto& value_type = expr->value.type(); 30 | 31 | if (value_type == typeid(nullptr)) { 32 | return "nil"; 33 | } else if (value_type == typeid(std::string)) { 34 | return std::any_cast(expr->value); 35 | } else if (value_type == typeid(double)) { 36 | return std::to_string(std::any_cast(expr->value)); 37 | } else if (value_type == typeid(bool)) { 38 | return std::any_cast(expr->value) ? "true" : "false"; 39 | } 40 | 41 | return "Error in visitLiteralExpr: literal type not recognized."; 42 | } 43 | 44 | std::any visitUnaryExpr(std::shared_ptr expr) override { 45 | return parenthesize(expr->op.lexeme, expr->right); 46 | } 47 | 48 | private: 49 | template 50 | std::string parenthesize(std::string_view name, E... expr) 51 | { 52 | assert((... && std::is_same_v>)); 53 | 54 | std::ostringstream builder; 55 | 56 | builder << "(" << name; 57 | ((builder << " " << print(expr)), ...); 58 | builder << ")"; 59 | 60 | return builder.str(); 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /chapter7/AstPrinterDriver.cpp: -------------------------------------------------------------------------------- 1 | #include "AstPrinter.h" 2 | 3 | int main(int argc, char* argv[]) { 4 | std::shared_ptr expression = std::make_shared( 5 | std::make_shared( 6 | Token{MINUS, "-", nullptr, 1}, 7 | std::make_shared(123.) 8 | ), 9 | Token{STAR, "*", nullptr, 1}, 10 | std::make_shared( 11 | std::make_shared(45.67))); 12 | 13 | std::cout << AstPrinter{}.print(expression) << "\n"; 14 | } -------------------------------------------------------------------------------- /chapter7/Error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "RuntimeError.h" 6 | #include "Token.h" 7 | 8 | inline bool hadError = false; 9 | inline bool hadRuntimeError = false; 10 | 11 | static void report(int line, std::string_view where, 12 | std::string_view message) { 13 | std::cerr << 14 | "[line " << line << "] Error" << where << ": " << message << 15 | "\n"; 16 | hadError = true; 17 | } 18 | 19 | void error(const Token& token, std::string_view message) { 20 | if (token.type == END_OF_FILE) { 21 | report(token.line, " at end", message); 22 | } else { 23 | report(token.line, " at '" + token.lexeme + "'", message); 24 | } 25 | } 26 | 27 | void error(int line, std::string_view message) { 28 | report(line, "", message); 29 | } 30 | 31 | void runtimeError(const RuntimeError& error) { 32 | std::cerr << error.what() << 33 | "\n[line " << error.token.line << "]\n"; 34 | hadRuntimeError = true; 35 | } 36 | -------------------------------------------------------------------------------- /chapter7/Expr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include // std::move 6 | #include 7 | #include "Token.h" 8 | 9 | struct Binary; 10 | struct Grouping; 11 | struct Literal; 12 | struct Unary; 13 | 14 | struct ExprVisitor { 15 | virtual std::any visitBinaryExpr(std::shared_ptr expr) = 0; 16 | virtual std::any visitGroupingExpr(std::shared_ptr expr) = 0; 17 | virtual std::any visitLiteralExpr(std::shared_ptr expr) = 0; 18 | virtual std::any visitUnaryExpr(std::shared_ptr expr) = 0; 19 | virtual ~ExprVisitor() = default; 20 | }; 21 | 22 | struct Expr { 23 | virtual std::any accept(ExprVisitor& visitor) = 0; 24 | }; 25 | 26 | struct Binary: Expr, public std::enable_shared_from_this { 27 | Binary(std::shared_ptr left, Token op, std::shared_ptr right) 28 | : left{std::move(left)}, op{std::move(op)}, right{std::move(right)} 29 | {} 30 | 31 | std::any accept(ExprVisitor& visitor) override { 32 | return visitor.visitBinaryExpr(shared_from_this()); 33 | } 34 | 35 | const std::shared_ptr left; 36 | const Token op; 37 | const std::shared_ptr right; 38 | }; 39 | 40 | struct Grouping: Expr, public std::enable_shared_from_this { 41 | Grouping(std::shared_ptr expression) 42 | : expression{std::move(expression)} 43 | {} 44 | 45 | std::any accept(ExprVisitor& visitor) override { 46 | return visitor.visitGroupingExpr(shared_from_this()); 47 | } 48 | 49 | const std::shared_ptr expression; 50 | }; 51 | 52 | struct Literal: Expr, public std::enable_shared_from_this { 53 | Literal(std::any value) 54 | : value{std::move(value)} 55 | {} 56 | 57 | std::any accept(ExprVisitor& visitor) override { 58 | return visitor.visitLiteralExpr(shared_from_this()); 59 | } 60 | 61 | const std::any value; 62 | }; 63 | 64 | struct Unary: Expr, public std::enable_shared_from_this { 65 | Unary(Token op, std::shared_ptr right) 66 | : op{std::move(op)}, right{std::move(right)} 67 | {} 68 | 69 | std::any accept(ExprVisitor& visitor) override { 70 | return visitor.visitUnaryExpr(shared_from_this()); 71 | } 72 | 73 | const Token op; 74 | const std::shared_ptr right; 75 | }; 76 | 77 | -------------------------------------------------------------------------------- /chapter7/Lox.cpp: -------------------------------------------------------------------------------- 1 | #include // std::strerror 2 | #include // readFile 3 | #include // std::getline 4 | #include 5 | #include 6 | #include "Error.h" 7 | #include "Interpreter.h" 8 | #include "Parser.h" 9 | #include "Scanner.h" 10 | 11 | std::string readFile(std::string_view path) { 12 | std::ifstream file{path.data(), std::ios::in | std::ios::binary | 13 | std::ios::ate}; 14 | if (!file) { 15 | std::cerr << "Failed to open file " << path << ": " 16 | << std::strerror(errno) << "\n"; 17 | std::exit(74); 18 | }; 19 | 20 | std::string contents; 21 | contents.resize(file.tellg()); 22 | 23 | file.seekg(0, std::ios::beg); 24 | file.read(contents.data(), contents.size()); 25 | 26 | return contents; 27 | } 28 | 29 | Interpreter interpreter{}; 30 | 31 | void run(std::string_view source) { 32 | Scanner scanner {source}; 33 | std::vector tokens = scanner.scanTokens(); 34 | Parser parser{tokens}; 35 | std::shared_ptr expression = parser.parse(); 36 | 37 | // Stop if there was a syntax error. 38 | if (hadError) return; 39 | 40 | interpreter.interpret(expression); 41 | } 42 | 43 | void runFile(std::string_view path) { 44 | std::string contents = readFile(path); 45 | run(contents); 46 | 47 | // Indicate an error in the exit code. 48 | if (hadError) std::exit(65); 49 | if (hadRuntimeError) std::exit(70); 50 | } 51 | 52 | void runPrompt() { 53 | for (;;) { 54 | std::cout << "> "; 55 | std::string line; 56 | if (!std::getline(std::cin, line)) break; 57 | run(line); 58 | hadError = false; 59 | } 60 | } 61 | 62 | int main(int argc, char* argv[]) { 63 | if (argc > 2) { 64 | std::cout << "Usage: jlox [script]\n"; 65 | std::exit(64); 66 | } else if (argc == 2) { 67 | runFile(argv[1]); 68 | } else { 69 | runPrompt(); 70 | } 71 | } -------------------------------------------------------------------------------- /chapter7/Makefile: -------------------------------------------------------------------------------- 1 | CXX := g++ 2 | CXXFLAGS := -ggdb -std=c++17 3 | CPPFLAGS := -MMD 4 | 5 | COMPILE := $(CXX) $(CXXFLAGS) $(CPPFLAGS) 6 | 7 | SRCS := AstPrinterDriver.cpp GenerateAst.cpp Lox.cpp 8 | DEPS := $(SRCS:.cpp=.d) 9 | 10 | 11 | jlox: Expr.h Lox.o 12 | @$(COMPILE) Lox.o -o $@ 13 | 14 | 15 | ast_printer: Expr.h AstPrinterDriver.o 16 | @$(COMPILE) AstPrinterDriver.o -o $@ 17 | 18 | 19 | generate_ast: GenerateAst.o 20 | @$(COMPILE) $< -o $@ 21 | 22 | 23 | Expr.h: generate_ast 24 | @./generate_ast . 25 | 26 | 27 | .PHONY: clean 28 | clean: 29 | rm -f *.d *.o ast_printer generate_ast jlox 30 | 31 | 32 | -include $(DEPS) 33 | 34 | 35 | .PHONY: test-expressions 36 | test-expressions: 37 | @make jlox >/dev/null 38 | @echo "testing jlox with test-expressions.lox ..." 39 | @./jlox tests/test-expressions.lox 2>&1 | diff -u --color tests/test-expressions.lox.expected -; 40 | 41 | 42 | .PHONY: test-expressions2 43 | test-expressions2: 44 | @make jlox >/dev/null 45 | @echo "testing jlox with test-expressions2.lox ..." 46 | @./jlox tests/test-expressions2.lox | diff -u --color tests/test-expressions2.lox.expected -; -------------------------------------------------------------------------------- /chapter7/RuntimeError.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Token.h" 5 | 6 | class RuntimeError: public std::runtime_error { 7 | public: 8 | const Token& token; 9 | 10 | RuntimeError(const Token& token, std::string_view message) 11 | : std::runtime_error{message.data()}, token{token} 12 | {} 13 | }; -------------------------------------------------------------------------------- /chapter7/Token.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include // std::move 6 | #include "TokenType.h" 7 | 8 | class Token { 9 | public: 10 | const TokenType type; 11 | const std::string lexeme; 12 | const std::any literal; 13 | const int line; 14 | 15 | Token(TokenType type, std::string lexeme, std::any literal, 16 | int line) 17 | : type{type}, lexeme{std::move(lexeme)}, 18 | literal{std::move(literal)}, line{line} 19 | {} 20 | 21 | std::string toString() const { 22 | std::string literal_text; 23 | 24 | switch (type) { 25 | case (IDENTIFIER): 26 | literal_text = lexeme; 27 | break; 28 | case (STRING): 29 | literal_text = std::any_cast(literal); 30 | break; 31 | case (NUMBER): 32 | literal_text = std::to_string(std::any_cast(literal)); 33 | break; 34 | case (TRUE): 35 | literal_text = "true"; 36 | break; 37 | case (FALSE): 38 | literal_text = "false"; 39 | break; 40 | default: 41 | literal_text = "nil"; 42 | } 43 | 44 | return ::toString(type) + " " + lexeme + " " + literal_text; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /chapter7/TokenType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | enum TokenType { 6 | // Single-character tokens. 7 | LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, 8 | COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, 9 | 10 | // One or two character tokens. 11 | BANG, BANG_EQUAL, 12 | EQUAL, EQUAL_EQUAL, 13 | GREATER, GREATER_EQUAL, 14 | LESS, LESS_EQUAL, 15 | 16 | // Literals. 17 | IDENTIFIER, STRING, NUMBER, 18 | 19 | // Keywords. 20 | AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR, 21 | PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE, 22 | 23 | END_OF_FILE 24 | }; 25 | 26 | std::string toString(TokenType type) { 27 | static const std::string strings[] = { 28 | "LEFT_PAREN", "RIGHT_PAREN", "LEFT_BRACE", "RIGHT_BRACE", 29 | "COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON", "SLASH", "STAR", 30 | "BANG", "BANG_EQUAL", 31 | "EQUAL", "EQUAL_EQUAL", 32 | "GREATER", "GREATER_EQUAL", 33 | "LESS", "LESS_EQUAL", 34 | "IDENTIFIER", "STRING", "NUMBER", 35 | "AND", "CLASS", "ELSE", "FALSE", "FUN", "FOR", "IF", "NIL", "OR", 36 | "PRINT", "RETURN", "SUPER", "THIS", "TRUE", "VAR", "WHILE", 37 | "END_OF_FILE" 38 | }; 39 | 40 | return strings[static_cast(type)]; 41 | } 42 | -------------------------------------------------------------------------------- /chapter7/tests/test-expressions.lox: -------------------------------------------------------------------------------- 1 | 2 * (3 / -"muffin") 2 | -------------------------------------------------------------------------------- /chapter7/tests/test-expressions.lox.expected: -------------------------------------------------------------------------------- 1 | Operand must be a number. 2 | [line 1] 3 | -------------------------------------------------------------------------------- /chapter7/tests/test-expressions2.lox: -------------------------------------------------------------------------------- 1 | -123 * (45.67) 2 | -------------------------------------------------------------------------------- /chapter7/tests/test-expressions2.lox.expected: -------------------------------------------------------------------------------- 1 | -5617.410000 2 | -------------------------------------------------------------------------------- /chapter8/AstPrinter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "Expr.h" 11 | 12 | class AstPrinter: public ExprVisitor { 13 | public: 14 | std::string print(std::shared_ptr expr) { 15 | return std::any_cast(expr->accept(*this)); 16 | } 17 | 18 | std::any visitBinaryExpr(std::shared_ptr expr) override { 19 | return parenthesize(expr->op.lexeme, 20 | expr->left, expr->right); 21 | } 22 | 23 | std::any visitGroupingExpr( 24 | std::shared_ptr expr) override { 25 | return parenthesize("group", expr->expression); 26 | } 27 | 28 | std::any visitLiteralExpr(std::shared_ptr expr) override { 29 | auto& value_type = expr->value.type(); 30 | 31 | if (value_type == typeid(nullptr)) { 32 | return "nil"; 33 | } else if (value_type == typeid(std::string)) { 34 | return std::any_cast(expr->value); 35 | } else if (value_type == typeid(double)) { 36 | return std::to_string(std::any_cast(expr->value)); 37 | } else if (value_type == typeid(bool)) { 38 | return std::any_cast(expr->value) ? "true" : "false"; 39 | } 40 | 41 | return "Error in visitLiteralExpr: literal type not recognized."; 42 | } 43 | 44 | std::any visitUnaryExpr(std::shared_ptr expr) override { 45 | return parenthesize(expr->op.lexeme, expr->right); 46 | } 47 | 48 | private: 49 | template 50 | std::string parenthesize(std::string_view name, E... expr) 51 | { 52 | assert((... && std::is_same_v>)); 53 | 54 | std::ostringstream builder; 55 | 56 | builder << "(" << name; 57 | ((builder << " " << print(expr)), ...); 58 | builder << ")"; 59 | 60 | return builder.str(); 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /chapter8/AstPrinterDriver.cpp: -------------------------------------------------------------------------------- 1 | #include "AstPrinter.h" 2 | 3 | int main(int argc, char* argv[]) { 4 | std::shared_ptr expression = std::make_shared( 5 | std::make_shared( 6 | Token{MINUS, "-", nullptr, 1}, 7 | std::make_shared(123.) 8 | ), 9 | Token{STAR, "*", nullptr, 1}, 10 | std::make_shared( 11 | std::make_shared(45.67))); 12 | 13 | std::cout << AstPrinter{}.print(expression) << "\n"; 14 | } -------------------------------------------------------------------------------- /chapter8/Environment.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include // less 5 | #include 6 | #include 7 | #include 8 | #include // std::move 9 | #include "Error.h" 10 | #include "Token.h" 11 | 12 | class Environment: public std::enable_shared_from_this { 13 | std::shared_ptr enclosing; 14 | std::map values; 15 | 16 | public: 17 | Environment() 18 | : enclosing{nullptr} 19 | {} 20 | 21 | Environment(std::shared_ptr enclosing) 22 | : enclosing{std::move(enclosing)} 23 | {} 24 | 25 | std::any get(const Token& name) { 26 | auto elem = values.find(name.lexeme); 27 | if (elem != values.end()) { 28 | return elem->second; 29 | } 30 | 31 | if (enclosing != nullptr) return enclosing->get(name); 32 | 33 | throw RuntimeError(name, 34 | "Undefined variable '" + name.lexeme + "'."); 35 | } 36 | 37 | void assign(const Token& name, std::any value) { 38 | auto elem = values.find(name.lexeme); 39 | if (elem != values.end()) { 40 | elem->second = std::move(value); 41 | return; 42 | } 43 | 44 | if (enclosing != nullptr) { 45 | enclosing->assign(name, std::move(value)); 46 | return; 47 | } 48 | 49 | throw RuntimeError(name, 50 | "Undefined variable '" + name.lexeme + "'."); 51 | } 52 | 53 | void define(const std::string& name, std::any value) { 54 | values[name] = std::move(value); 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /chapter8/Error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "RuntimeError.h" 6 | #include "Token.h" 7 | 8 | inline bool hadError = false; 9 | inline bool hadRuntimeError = false; 10 | 11 | static void report(int line, std::string_view where, 12 | std::string_view message) { 13 | std::cerr << 14 | "[line " << line << "] Error" << where << ": " << message << 15 | "\n"; 16 | hadError = true; 17 | } 18 | 19 | void error(const Token& token, std::string_view message) { 20 | if (token.type == END_OF_FILE) { 21 | report(token.line, " at end", message); 22 | } else { 23 | report(token.line, " at '" + token.lexeme + "'", message); 24 | } 25 | } 26 | 27 | void error(int line, std::string_view message) { 28 | report(line, "", message); 29 | } 30 | 31 | void runtimeError(const RuntimeError& error) { 32 | std::cerr << error.what() << 33 | "\n[line " << error.token.line << "]\n"; 34 | hadRuntimeError = true; 35 | } 36 | -------------------------------------------------------------------------------- /chapter8/Lox.cpp: -------------------------------------------------------------------------------- 1 | #include // std::strerror 2 | #include // readFile 3 | #include // std::getline 4 | #include 5 | #include 6 | #include "Error.h" 7 | #include "Interpreter.h" 8 | #include "Parser.h" 9 | #include "Scanner.h" 10 | 11 | std::string readFile(std::string_view path) { 12 | std::ifstream file{path.data(), std::ios::in | std::ios::binary | 13 | std::ios::ate}; 14 | if (!file) { 15 | std::cerr << "Failed to open file " << path << ": " 16 | << std::strerror(errno) << "\n"; 17 | std::exit(74); 18 | }; 19 | 20 | std::string contents; 21 | contents.resize(file.tellg()); 22 | 23 | file.seekg(0, std::ios::beg); 24 | file.read(contents.data(), contents.size()); 25 | 26 | return contents; 27 | } 28 | 29 | Interpreter interpreter{}; 30 | 31 | void run(std::string_view source) { 32 | Scanner scanner {source}; 33 | std::vector tokens = scanner.scanTokens(); 34 | Parser parser{tokens}; 35 | std::vector> statements = parser.parse(); 36 | 37 | // Stop if there was a syntax error. 38 | if (hadError) return; 39 | 40 | // Stop if there was a resolution error. 41 | if (hadError) return; 42 | 43 | interpreter.interpret(statements); 44 | } 45 | 46 | void runFile(std::string_view path) { 47 | std::string contents = readFile(path); 48 | run(contents); 49 | 50 | // Indicate an error in the exit code. 51 | if (hadError) std::exit(65); 52 | if (hadRuntimeError) std::exit(70); 53 | } 54 | 55 | void runPrompt() { 56 | for (;;) { 57 | std::cout << "> "; 58 | std::string line; 59 | if (!std::getline(std::cin, line)) break; 60 | run(line); 61 | hadError = false; 62 | } 63 | } 64 | 65 | int main(int argc, char* argv[]) { 66 | if (argc > 2) { 67 | std::cout << "Usage: jlox [script]\n"; 68 | std::exit(64); 69 | } else if (argc == 2) { 70 | runFile(argv[1]); 71 | } else { 72 | runPrompt(); 73 | } 74 | } -------------------------------------------------------------------------------- /chapter8/Makefile: -------------------------------------------------------------------------------- 1 | CXX := g++ 2 | CXXFLAGS := -ggdb -std=c++17 3 | CPPFLAGS := -MMD 4 | 5 | COMPILE := $(CXX) $(CXXFLAGS) $(CPPFLAGS) 6 | 7 | SRCS := AstPrinterDriver.cpp GenerateAst.cpp Lox.cpp 8 | DEPS := $(SRCS:.cpp=.d) 9 | 10 | 11 | jlox: Expr.h Stmt.h Lox.o 12 | @$(COMPILE) Lox.o -o $@ 13 | 14 | 15 | ast_printer: Expr.h Stmt.h AstPrinterDriver.o 16 | @$(COMPILE) AstPrinterDriver.o -o $@ 17 | 18 | 19 | generate_ast: GenerateAst.o 20 | @$(COMPILE) $< -o $@ 21 | 22 | 23 | Expr.h Stmt.h: generate_ast 24 | @./generate_ast . 25 | 26 | 27 | .PHONY: clean 28 | clean: 29 | rm -f *.d *.o ast_printer generate_ast jlox 30 | 31 | 32 | -include $(DEPS) 33 | 34 | 35 | define make_test 36 | .PHONY: $(1) 37 | $(1): 38 | @make jlox >/dev/null 39 | @echo "testing jlox with $(1).lox ..." 40 | @./jlox tests/$(1).lox | diff -u --color tests/$(1).lox.expected -; 41 | endef 42 | 43 | 44 | TESTS = \ 45 | test-statements \ 46 | test-statements2 \ 47 | test-statements3 \ 48 | test-statements4 \ 49 | test-statements5 \ 50 | test-statements6 \ 51 | 52 | 53 | $(foreach test, $(TESTS), $(eval $(call make_test,$(test)))) 54 | 55 | 56 | .PHONY: test-all 57 | test-all: 58 | @for test in $(TESTS); do \ 59 | make -s $$test; \ 60 | done 61 | -------------------------------------------------------------------------------- /chapter8/RuntimeError.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Token.h" 5 | 6 | class RuntimeError: public std::runtime_error { 7 | public: 8 | const Token& token; 9 | 10 | RuntimeError(const Token& token, std::string_view message) 11 | : std::runtime_error{message.data()}, token{token} 12 | {} 13 | }; -------------------------------------------------------------------------------- /chapter8/Stmt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include // std::move 6 | #include 7 | #include "Token.h" 8 | 9 | #include "Expr.h" 10 | 11 | struct Block; 12 | struct Expression; 13 | struct Print; 14 | struct Var; 15 | 16 | struct StmtVisitor { 17 | virtual std::any visitBlockStmt(std::shared_ptr stmt) = 0; 18 | virtual std::any visitExpressionStmt(std::shared_ptr stmt) = 0; 19 | virtual std::any visitPrintStmt(std::shared_ptr stmt) = 0; 20 | virtual std::any visitVarStmt(std::shared_ptr stmt) = 0; 21 | virtual ~StmtVisitor() = default; 22 | }; 23 | 24 | struct Stmt { 25 | virtual std::any accept(StmtVisitor& visitor) = 0; 26 | }; 27 | 28 | struct Block: Stmt, public std::enable_shared_from_this { 29 | Block(std::vector> statements) 30 | : statements{std::move(statements)} 31 | {} 32 | 33 | std::any accept(StmtVisitor& visitor) override { 34 | return visitor.visitBlockStmt(shared_from_this()); 35 | } 36 | 37 | const std::vector> statements; 38 | }; 39 | 40 | struct Expression: Stmt, public std::enable_shared_from_this { 41 | Expression(std::shared_ptr expression) 42 | : expression{std::move(expression)} 43 | {} 44 | 45 | std::any accept(StmtVisitor& visitor) override { 46 | return visitor.visitExpressionStmt(shared_from_this()); 47 | } 48 | 49 | const std::shared_ptr expression; 50 | }; 51 | 52 | struct Print: Stmt, public std::enable_shared_from_this { 53 | Print(std::shared_ptr expression) 54 | : expression{std::move(expression)} 55 | {} 56 | 57 | std::any accept(StmtVisitor& visitor) override { 58 | return visitor.visitPrintStmt(shared_from_this()); 59 | } 60 | 61 | const std::shared_ptr expression; 62 | }; 63 | 64 | struct Var: Stmt, public std::enable_shared_from_this { 65 | Var(Token name, std::shared_ptr initializer) 66 | : name{std::move(name)}, initializer{std::move(initializer)} 67 | {} 68 | 69 | std::any accept(StmtVisitor& visitor) override { 70 | return visitor.visitVarStmt(shared_from_this()); 71 | } 72 | 73 | const Token name; 74 | const std::shared_ptr initializer; 75 | }; 76 | 77 | -------------------------------------------------------------------------------- /chapter8/Token.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include // std::move 6 | #include "TokenType.h" 7 | 8 | class Token { 9 | public: 10 | const TokenType type; 11 | const std::string lexeme; 12 | const std::any literal; 13 | const int line; 14 | 15 | Token(TokenType type, std::string lexeme, std::any literal, 16 | int line) 17 | : type{type}, lexeme{std::move(lexeme)}, 18 | literal{std::move(literal)}, line{line} 19 | {} 20 | 21 | std::string toString() const { 22 | std::string literal_text; 23 | 24 | switch (type) { 25 | case (IDENTIFIER): 26 | literal_text = lexeme; 27 | break; 28 | case (STRING): 29 | literal_text = std::any_cast(literal); 30 | break; 31 | case (NUMBER): 32 | literal_text = std::to_string(std::any_cast(literal)); 33 | break; 34 | case (TRUE): 35 | literal_text = "true"; 36 | break; 37 | case (FALSE): 38 | literal_text = "false"; 39 | break; 40 | default: 41 | literal_text = "nil"; 42 | } 43 | 44 | return ::toString(type) + " " + lexeme + " " + literal_text; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /chapter8/TokenType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | enum TokenType { 6 | // Single-character tokens. 7 | LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, 8 | COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, 9 | 10 | // One or two character tokens. 11 | BANG, BANG_EQUAL, 12 | EQUAL, EQUAL_EQUAL, 13 | GREATER, GREATER_EQUAL, 14 | LESS, LESS_EQUAL, 15 | 16 | // Literals. 17 | IDENTIFIER, STRING, NUMBER, 18 | 19 | // Keywords. 20 | AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR, 21 | PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE, 22 | 23 | END_OF_FILE 24 | }; 25 | 26 | std::string toString(TokenType type) { 27 | static const std::string strings[] = { 28 | "LEFT_PAREN", "RIGHT_PAREN", "LEFT_BRACE", "RIGHT_BRACE", 29 | "COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON", "SLASH", "STAR", 30 | "BANG", "BANG_EQUAL", 31 | "EQUAL", "EQUAL_EQUAL", 32 | "GREATER", "GREATER_EQUAL", 33 | "LESS", "LESS_EQUAL", 34 | "IDENTIFIER", "STRING", "NUMBER", 35 | "AND", "CLASS", "ELSE", "FALSE", "FUN", "FOR", "IF", "NIL", "OR", 36 | "PRINT", "RETURN", "SUPER", "THIS", "TRUE", "VAR", "WHILE", 37 | "END_OF_FILE" 38 | }; 39 | 40 | return strings[static_cast(type)]; 41 | } 42 | -------------------------------------------------------------------------------- /chapter8/challenge2/AstPrinter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "Expr.h" 11 | 12 | class AstPrinter: public ExprVisitor { 13 | public: 14 | std::string print(std::shared_ptr expr) { 15 | return std::any_cast(expr->accept(*this)); 16 | } 17 | 18 | std::any visitBinaryExpr(std::shared_ptr expr) override { 19 | return parenthesize(expr->op.lexeme, 20 | expr->left, expr->right); 21 | } 22 | 23 | std::any visitGroupingExpr( 24 | std::shared_ptr expr) override { 25 | return parenthesize("group", expr->expression); 26 | } 27 | 28 | std::any visitLiteralExpr(std::shared_ptr expr) override { 29 | auto& value_type = expr->value.type(); 30 | 31 | if (value_type == typeid(nullptr)) { 32 | return "nil"; 33 | } else if (value_type == typeid(std::string)) { 34 | return std::any_cast(expr->value); 35 | } else if (value_type == typeid(double)) { 36 | return std::to_string(std::any_cast(expr->value)); 37 | } else if (value_type == typeid(bool)) { 38 | return std::any_cast(expr->value) ? "true" : "false"; 39 | } 40 | 41 | return "Error in visitLiteralExpr: literal type not recognized."; 42 | } 43 | 44 | std::any visitUnaryExpr(std::shared_ptr expr) override { 45 | return parenthesize(expr->op.lexeme, expr->right); 46 | } 47 | 48 | private: 49 | template 50 | std::string parenthesize(std::string_view name, E... expr) 51 | { 52 | assert((... && std::is_same_v>)); 53 | 54 | std::ostringstream builder; 55 | 56 | builder << "(" << name; 57 | ((builder << " " << print(expr)), ...); 58 | builder << ")"; 59 | 60 | return builder.str(); 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /chapter8/challenge2/AstPrinterDriver.cpp: -------------------------------------------------------------------------------- 1 | #include "AstPrinter.h" 2 | 3 | int main(int argc, char* argv[]) { 4 | std::shared_ptr expression = std::make_shared( 5 | std::make_shared( 6 | Token{MINUS, "-", nullptr, 1}, 7 | std::make_shared(123.) 8 | ), 9 | Token{STAR, "*", nullptr, 1}, 10 | std::make_shared( 11 | std::make_shared(45.67))); 12 | 13 | std::cout << AstPrinter{}.print(expression) << "\n"; 14 | } -------------------------------------------------------------------------------- /chapter8/challenge2/Environment.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include // less 5 | #include 6 | #include 7 | #include 8 | #include // std::move 9 | #include "Error.h" 10 | #include "Token.h" 11 | 12 | class Environment: public std::enable_shared_from_this { 13 | std::shared_ptr enclosing; 14 | std::map values; 15 | 16 | public: 17 | Environment() 18 | : enclosing{nullptr} 19 | {} 20 | 21 | Environment(std::shared_ptr enclosing) 22 | : enclosing{std::move(enclosing)} 23 | {} 24 | 25 | std::any get(const Token& name) { 26 | auto elem = values.find(name.lexeme); 27 | if (elem != values.end()) { 28 | return elem->second; 29 | } 30 | 31 | if (enclosing != nullptr) return enclosing->get(name); 32 | 33 | throw RuntimeError(name, 34 | "Undefined variable '" + name.lexeme + "'."); 35 | } 36 | 37 | void assign(const Token& name, std::any value) { 38 | auto elem = values.find(name.lexeme); 39 | if (elem != values.end()) { 40 | elem->second = std::move(value); 41 | return; 42 | } 43 | 44 | if (enclosing != nullptr) { 45 | enclosing->assign(name, std::move(value)); 46 | return; 47 | } 48 | 49 | throw RuntimeError(name, 50 | "Undefined variable '" + name.lexeme + "'."); 51 | } 52 | 53 | void define(const std::string& name, std::any value) { 54 | values[name] = std::move(value); 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /chapter8/challenge2/Error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "RuntimeError.h" 6 | #include "Token.h" 7 | 8 | inline bool hadError = false; 9 | inline bool hadRuntimeError = false; 10 | 11 | static void report(int line, std::string_view where, 12 | std::string_view message) { 13 | std::cerr << 14 | "[line " << line << "] Error" << where << ": " << message << 15 | "\n"; 16 | hadError = true; 17 | } 18 | 19 | void error(const Token& token, std::string_view message) { 20 | if (token.type == END_OF_FILE) { 21 | report(token.line, " at end", message); 22 | } else { 23 | report(token.line, " at '" + token.lexeme + "'", message); 24 | } 25 | } 26 | 27 | void error(int line, std::string_view message) { 28 | report(line, "", message); 29 | } 30 | 31 | void runtimeError(const RuntimeError& error) { 32 | std::cerr << error.what() << 33 | "\n[line " << error.token.line << "]\n"; 34 | hadRuntimeError = true; 35 | } 36 | -------------------------------------------------------------------------------- /chapter8/challenge2/Lox.cpp: -------------------------------------------------------------------------------- 1 | #include // std::strerror 2 | #include // readFile 3 | #include // std::getline 4 | #include 5 | #include 6 | #include "Error.h" 7 | #include "Interpreter.h" 8 | #include "Parser.h" 9 | #include "Scanner.h" 10 | 11 | std::string readFile(std::string_view path) { 12 | std::ifstream file{path.data(), std::ios::in | std::ios::binary | 13 | std::ios::ate}; 14 | if (!file) { 15 | std::cerr << "Failed to open file " << path << ": " 16 | << std::strerror(errno) << "\n"; 17 | std::exit(74); 18 | }; 19 | 20 | std::string contents; 21 | contents.resize(file.tellg()); 22 | 23 | file.seekg(0, std::ios::beg); 24 | file.read(contents.data(), contents.size()); 25 | 26 | return contents; 27 | } 28 | 29 | Interpreter interpreter{}; 30 | 31 | void run(std::string_view source) { 32 | Scanner scanner {source}; 33 | std::vector tokens = scanner.scanTokens(); 34 | Parser parser{tokens}; 35 | std::vector> statements = parser.parse(); 36 | 37 | // Stop if there was a syntax error. 38 | if (hadError) return; 39 | 40 | // Stop if there was a resolution error. 41 | if (hadError) return; 42 | 43 | interpreter.interpret(statements); 44 | } 45 | 46 | void runFile(std::string_view path) { 47 | std::string contents = readFile(path); 48 | run(contents); 49 | 50 | // Indicate an error in the exit code. 51 | if (hadError) std::exit(65); 52 | if (hadRuntimeError) std::exit(70); 53 | } 54 | 55 | void runPrompt() { 56 | for (;;) { 57 | std::cout << "> "; 58 | std::string line; 59 | if (!std::getline(std::cin, line)) break; 60 | run(line); 61 | hadError = false; 62 | } 63 | } 64 | 65 | int main(int argc, char* argv[]) { 66 | if (argc > 2) { 67 | std::cout << "Usage: jlox [script]\n"; 68 | std::exit(64); 69 | } else if (argc == 2) { 70 | runFile(argv[1]); 71 | } else { 72 | runPrompt(); 73 | } 74 | } -------------------------------------------------------------------------------- /chapter8/challenge2/Makefile: -------------------------------------------------------------------------------- 1 | CXX := g++ 2 | CXXFLAGS := -ggdb -std=c++17 3 | CPPFLAGS := -MMD 4 | 5 | COMPILE := $(CXX) $(CXXFLAGS) $(CPPFLAGS) 6 | 7 | SRCS := AstPrinterDriver.cpp GenerateAst.cpp Lox.cpp 8 | DEPS := $(SRCS:.cpp=.d) 9 | 10 | 11 | jlox: Expr.h Stmt.h Lox.o 12 | @$(COMPILE) Lox.o -o $@ 13 | 14 | 15 | ast_printer: Expr.h Stmt.h AstPrinterDriver.o 16 | @$(COMPILE) AstPrinterDriver.o -o $@ 17 | 18 | 19 | generate_ast: GenerateAst.o 20 | @$(COMPILE) $< -o $@ 21 | 22 | 23 | Expr.h Stmt.h: generate_ast 24 | @./generate_ast . 25 | 26 | 27 | .PHONY: clean 28 | clean: 29 | rm -f *.d *.o ast_printer generate_ast jlox 30 | 31 | 32 | -include $(DEPS) 33 | 34 | 35 | define make_test 36 | .PHONY: $(1) 37 | $(1): 38 | @make jlox >/dev/null 39 | @echo "testing jlox with $(1).lox ..." 40 | @./jlox tests/$(1).lox | diff -u --color tests/$(1).lox.expected -; 41 | endef 42 | 43 | 44 | define make_test_error 45 | .PHONY: $(1) 46 | $(1): 47 | @make jlox >/dev/null 48 | @echo "testing jlox with $(1).lox ..." 49 | @./jlox tests/$(1).lox 2>&1 | diff -u --color tests/$(1).lox.expected -; 50 | endef 51 | 52 | 53 | TESTS = \ 54 | test-statements \ 55 | test-statements2 \ 56 | test-statements4 \ 57 | test-statements5 \ 58 | test-statements6 \ 59 | 60 | 61 | TEST_ERRORS = \ 62 | test-challenge2 \ 63 | test-statements3 \ 64 | 65 | 66 | $(foreach test, $(TESTS), $(eval $(call make_test,$(test)))) 67 | $(foreach test, $(TEST_ERRORS), $(eval $(call make_test_error,$(test)))) 68 | 69 | 70 | .PHONY: test-all 71 | test-all: 72 | @for test in $(TESTS) $(TEST_ERRORS); do \ 73 | make -s $$test; \ 74 | done 75 | -------------------------------------------------------------------------------- /chapter8/challenge2/RuntimeError.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Token.h" 5 | 6 | class RuntimeError: public std::runtime_error { 7 | public: 8 | const Token& token; 9 | 10 | RuntimeError(const Token& token, std::string_view message) 11 | : std::runtime_error{message.data()}, token{token} 12 | {} 13 | }; -------------------------------------------------------------------------------- /chapter8/challenge2/Stmt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include // std::move 6 | #include 7 | #include "Token.h" 8 | 9 | #include "Expr.h" 10 | 11 | struct Block; 12 | struct Expression; 13 | struct Print; 14 | struct Var; 15 | 16 | struct StmtVisitor { 17 | virtual std::any visitBlockStmt(std::shared_ptr stmt) = 0; 18 | virtual std::any visitExpressionStmt(std::shared_ptr stmt) = 0; 19 | virtual std::any visitPrintStmt(std::shared_ptr stmt) = 0; 20 | virtual std::any visitVarStmt(std::shared_ptr stmt) = 0; 21 | virtual ~StmtVisitor() = default; 22 | }; 23 | 24 | struct Stmt { 25 | virtual std::any accept(StmtVisitor& visitor) = 0; 26 | }; 27 | 28 | struct Block: Stmt, public std::enable_shared_from_this { 29 | Block(std::vector> statements) 30 | : statements{std::move(statements)} 31 | {} 32 | 33 | std::any accept(StmtVisitor& visitor) override { 34 | return visitor.visitBlockStmt(shared_from_this()); 35 | } 36 | 37 | const std::vector> statements; 38 | }; 39 | 40 | struct Expression: Stmt, public std::enable_shared_from_this { 41 | Expression(std::shared_ptr expression) 42 | : expression{std::move(expression)} 43 | {} 44 | 45 | std::any accept(StmtVisitor& visitor) override { 46 | return visitor.visitExpressionStmt(shared_from_this()); 47 | } 48 | 49 | const std::shared_ptr expression; 50 | }; 51 | 52 | struct Print: Stmt, public std::enable_shared_from_this { 53 | Print(std::shared_ptr expression) 54 | : expression{std::move(expression)} 55 | {} 56 | 57 | std::any accept(StmtVisitor& visitor) override { 58 | return visitor.visitPrintStmt(shared_from_this()); 59 | } 60 | 61 | const std::shared_ptr expression; 62 | }; 63 | 64 | struct Var: Stmt, public std::enable_shared_from_this { 65 | Var(Token name, std::shared_ptr initializer) 66 | : name{std::move(name)}, initializer{std::move(initializer)} 67 | {} 68 | 69 | std::any accept(StmtVisitor& visitor) override { 70 | return visitor.visitVarStmt(shared_from_this()); 71 | } 72 | 73 | const Token name; 74 | const std::shared_ptr initializer; 75 | }; 76 | 77 | -------------------------------------------------------------------------------- /chapter8/challenge2/Token.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include // std::move 6 | #include "TokenType.h" 7 | 8 | class Token { 9 | public: 10 | const TokenType type; 11 | const std::string lexeme; 12 | const std::any literal; 13 | const int line; 14 | 15 | Token(TokenType type, std::string lexeme, std::any literal, 16 | int line) 17 | : type{type}, lexeme{std::move(lexeme)}, 18 | literal{std::move(literal)}, line{line} 19 | {} 20 | 21 | std::string toString() const { 22 | std::string literal_text; 23 | 24 | switch (type) { 25 | case (IDENTIFIER): 26 | literal_text = lexeme; 27 | break; 28 | case (STRING): 29 | literal_text = std::any_cast(literal); 30 | break; 31 | case (NUMBER): 32 | literal_text = std::to_string(std::any_cast(literal)); 33 | break; 34 | case (TRUE): 35 | literal_text = "true"; 36 | break; 37 | case (FALSE): 38 | literal_text = "false"; 39 | break; 40 | default: 41 | literal_text = "nil"; 42 | } 43 | 44 | return ::toString(type) + " " + lexeme + " " + literal_text; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /chapter8/challenge2/TokenType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | enum TokenType { 6 | // Single-character tokens. 7 | LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, 8 | COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, 9 | 10 | // One or two character tokens. 11 | BANG, BANG_EQUAL, 12 | EQUAL, EQUAL_EQUAL, 13 | GREATER, GREATER_EQUAL, 14 | LESS, LESS_EQUAL, 15 | 16 | // Literals. 17 | IDENTIFIER, STRING, NUMBER, 18 | 19 | // Keywords. 20 | AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR, 21 | PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE, 22 | 23 | END_OF_FILE 24 | }; 25 | 26 | std::string toString(TokenType type) { 27 | static const std::string strings[] = { 28 | "LEFT_PAREN", "RIGHT_PAREN", "LEFT_BRACE", "RIGHT_BRACE", 29 | "COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON", "SLASH", "STAR", 30 | "BANG", "BANG_EQUAL", 31 | "EQUAL", "EQUAL_EQUAL", 32 | "GREATER", "GREATER_EQUAL", 33 | "LESS", "LESS_EQUAL", 34 | "IDENTIFIER", "STRING", "NUMBER", 35 | "AND", "CLASS", "ELSE", "FALSE", "FUN", "FOR", "IF", "NIL", "OR", 36 | "PRINT", "RETURN", "SUPER", "THIS", "TRUE", "VAR", "WHILE", 37 | "END_OF_FILE" 38 | }; 39 | 40 | return strings[static_cast(type)]; 41 | } 42 | -------------------------------------------------------------------------------- /chapter8/challenge2/tests/test-challenge2.lox: -------------------------------------------------------------------------------- 1 | // No initializers. 2 | var a; 3 | var b; 4 | 5 | a = "assigned"; 6 | print a; // OK, was assigned first. 7 | 8 | print b; // Error! -------------------------------------------------------------------------------- /chapter8/challenge2/tests/test-challenge2.lox.expected: -------------------------------------------------------------------------------- 1 | assigned 2 | Variable not initialized. 3 | [line 8] 4 | -------------------------------------------------------------------------------- /chapter8/challenge2/tests/test-statements.lox: -------------------------------------------------------------------------------- 1 | print "one"; 2 | print true; 3 | print 2 + 1; 4 | -------------------------------------------------------------------------------- /chapter8/challenge2/tests/test-statements.lox.expected: -------------------------------------------------------------------------------- 1 | one 2 | true 3 | 3.000000 4 | -------------------------------------------------------------------------------- /chapter8/challenge2/tests/test-statements2.lox: -------------------------------------------------------------------------------- 1 | var a = "before"; 2 | print a; // "before". 3 | var a = "after"; 4 | print a; // "after". 5 | -------------------------------------------------------------------------------- /chapter8/challenge2/tests/test-statements2.lox.expected: -------------------------------------------------------------------------------- 1 | before 2 | after 3 | -------------------------------------------------------------------------------- /chapter8/challenge2/tests/test-statements3.lox: -------------------------------------------------------------------------------- 1 | var a; 2 | print a; // Error! 3 | -------------------------------------------------------------------------------- /chapter8/challenge2/tests/test-statements3.lox.expected: -------------------------------------------------------------------------------- 1 | Variable not initialized. 2 | [line 2] 3 | -------------------------------------------------------------------------------- /chapter8/challenge2/tests/test-statements4.lox: -------------------------------------------------------------------------------- 1 | var a = 1; 2 | var b = 2; 3 | print a + b; 4 | -------------------------------------------------------------------------------- /chapter8/challenge2/tests/test-statements4.lox.expected: -------------------------------------------------------------------------------- 1 | 3.000000 2 | -------------------------------------------------------------------------------- /chapter8/challenge2/tests/test-statements5.lox: -------------------------------------------------------------------------------- 1 | var a = 1; 2 | print a = 2; // "2". 3 | -------------------------------------------------------------------------------- /chapter8/challenge2/tests/test-statements5.lox.expected: -------------------------------------------------------------------------------- 1 | 2.000000 2 | -------------------------------------------------------------------------------- /chapter8/challenge2/tests/test-statements6.lox: -------------------------------------------------------------------------------- 1 | var a = "global a"; 2 | var b = "global b"; 3 | var c = "global c"; 4 | { 5 | var a = "outer a"; 6 | var b = "outer b"; 7 | { 8 | var a = "inner a"; 9 | print a; 10 | print b; 11 | print c; 12 | } 13 | print a; 14 | print b; 15 | print c; 16 | } 17 | print a; 18 | print b; 19 | print c; 20 | -------------------------------------------------------------------------------- /chapter8/challenge2/tests/test-statements6.lox.expected: -------------------------------------------------------------------------------- 1 | inner a 2 | outer b 3 | global c 4 | outer a 5 | outer b 6 | global c 7 | global a 8 | global b 9 | global c 10 | -------------------------------------------------------------------------------- /chapter8/tests/test-statements.lox: -------------------------------------------------------------------------------- 1 | print "one"; 2 | print true; 3 | print 2 + 1; 4 | -------------------------------------------------------------------------------- /chapter8/tests/test-statements.lox.expected: -------------------------------------------------------------------------------- 1 | one 2 | true 3 | 3.000000 4 | -------------------------------------------------------------------------------- /chapter8/tests/test-statements2.lox: -------------------------------------------------------------------------------- 1 | var a = "before"; 2 | print a; // "before". 3 | var a = "after"; 4 | print a; // "after". 5 | -------------------------------------------------------------------------------- /chapter8/tests/test-statements2.lox.expected: -------------------------------------------------------------------------------- 1 | before 2 | after 3 | -------------------------------------------------------------------------------- /chapter8/tests/test-statements3.lox: -------------------------------------------------------------------------------- 1 | var a; 2 | print a; // "nil". 3 | -------------------------------------------------------------------------------- /chapter8/tests/test-statements3.lox.expected: -------------------------------------------------------------------------------- 1 | nil 2 | -------------------------------------------------------------------------------- /chapter8/tests/test-statements4.lox: -------------------------------------------------------------------------------- 1 | var a = 1; 2 | var b = 2; 3 | print a + b; 4 | -------------------------------------------------------------------------------- /chapter8/tests/test-statements4.lox.expected: -------------------------------------------------------------------------------- 1 | 3.000000 2 | -------------------------------------------------------------------------------- /chapter8/tests/test-statements5.lox: -------------------------------------------------------------------------------- 1 | var a = 1; 2 | print a = 2; // "2". 3 | -------------------------------------------------------------------------------- /chapter8/tests/test-statements5.lox.expected: -------------------------------------------------------------------------------- 1 | 2.000000 2 | -------------------------------------------------------------------------------- /chapter8/tests/test-statements6.lox: -------------------------------------------------------------------------------- 1 | var a = "global a"; 2 | var b = "global b"; 3 | var c = "global c"; 4 | { 5 | var a = "outer a"; 6 | var b = "outer b"; 7 | { 8 | var a = "inner a"; 9 | print a; 10 | print b; 11 | print c; 12 | } 13 | print a; 14 | print b; 15 | print c; 16 | } 17 | print a; 18 | print b; 19 | print c; 20 | -------------------------------------------------------------------------------- /chapter8/tests/test-statements6.lox.expected: -------------------------------------------------------------------------------- 1 | inner a 2 | outer b 3 | global c 4 | outer a 5 | outer b 6 | global c 7 | global a 8 | global b 9 | global c 10 | -------------------------------------------------------------------------------- /chapter9/AstPrinter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "Expr.h" 11 | 12 | class AstPrinter: public ExprVisitor { 13 | public: 14 | std::string print(std::shared_ptr expr) { 15 | return std::any_cast(expr->accept(*this)); 16 | } 17 | 18 | std::any visitBinaryExpr(std::shared_ptr expr) override { 19 | return parenthesize(expr->op.lexeme, 20 | expr->left, expr->right); 21 | } 22 | 23 | std::any visitGroupingExpr( 24 | std::shared_ptr expr) override { 25 | return parenthesize("group", expr->expression); 26 | } 27 | 28 | std::any visitLiteralExpr(std::shared_ptr expr) override { 29 | auto& value_type = expr->value.type(); 30 | 31 | if (value_type == typeid(nullptr)) { 32 | return "nil"; 33 | } else if (value_type == typeid(std::string)) { 34 | return std::any_cast(expr->value); 35 | } else if (value_type == typeid(double)) { 36 | return std::to_string(std::any_cast(expr->value)); 37 | } else if (value_type == typeid(bool)) { 38 | return std::any_cast(expr->value) ? "true" : "false"; 39 | } 40 | 41 | return "Error in visitLiteralExpr: literal type not recognized."; 42 | } 43 | 44 | std::any visitUnaryExpr(std::shared_ptr expr) override { 45 | return parenthesize(expr->op.lexeme, expr->right); 46 | } 47 | 48 | private: 49 | template 50 | std::string parenthesize(std::string_view name, E... expr) 51 | { 52 | assert((... && std::is_same_v>)); 53 | 54 | std::ostringstream builder; 55 | 56 | builder << "(" << name; 57 | ((builder << " " << print(expr)), ...); 58 | builder << ")"; 59 | 60 | return builder.str(); 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /chapter9/AstPrinterDriver.cpp: -------------------------------------------------------------------------------- 1 | #include "AstPrinter.h" 2 | 3 | int main(int argc, char* argv[]) { 4 | std::shared_ptr expression = std::make_shared( 5 | std::make_shared( 6 | Token{MINUS, "-", nullptr, 1}, 7 | std::make_shared(123.) 8 | ), 9 | Token{STAR, "*", nullptr, 1}, 10 | std::make_shared( 11 | std::make_shared(45.67))); 12 | 13 | std::cout << AstPrinter{}.print(expression) << "\n"; 14 | } -------------------------------------------------------------------------------- /chapter9/Environment.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include // less 5 | #include 6 | #include 7 | #include 8 | #include // std::move 9 | #include "Error.h" 10 | #include "Token.h" 11 | 12 | class Environment: public std::enable_shared_from_this { 13 | friend class Interpreter; 14 | 15 | std::shared_ptr enclosing; 16 | std::map values; 17 | 18 | public: 19 | Environment() 20 | : enclosing{nullptr} 21 | {} 22 | 23 | Environment(std::shared_ptr enclosing) 24 | : enclosing{std::move(enclosing)} 25 | {} 26 | 27 | std::any get(const Token& name) { 28 | auto elem = values.find(name.lexeme); 29 | if (elem != values.end()) { 30 | return elem->second; 31 | } 32 | 33 | if (enclosing != nullptr) return enclosing->get(name); 34 | 35 | throw RuntimeError(name, 36 | "Undefined variable '" + name.lexeme + "'."); 37 | } 38 | 39 | void assign(const Token& name, std::any value) { 40 | auto elem = values.find(name.lexeme); 41 | if (elem != values.end()) { 42 | elem->second = std::move(value); 43 | return; 44 | } 45 | 46 | if (enclosing != nullptr) { 47 | enclosing->assign(name, std::move(value)); 48 | return; 49 | } 50 | 51 | throw RuntimeError(name, 52 | "Undefined variable '" + name.lexeme + "'."); 53 | } 54 | 55 | void define(const std::string& name, std::any value) { 56 | values[name] = std::move(value); 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /chapter9/Error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "RuntimeError.h" 6 | #include "Token.h" 7 | 8 | inline bool hadError = false; 9 | inline bool hadRuntimeError = false; 10 | 11 | static void report(int line, std::string_view where, 12 | std::string_view message) { 13 | std::cerr << 14 | "[line " << line << "] Error" << where << ": " << message << 15 | "\n"; 16 | hadError = true; 17 | } 18 | 19 | void error(const Token& token, std::string_view message) { 20 | if (token.type == END_OF_FILE) { 21 | report(token.line, " at end", message); 22 | } else { 23 | report(token.line, " at '" + token.lexeme + "'", message); 24 | } 25 | } 26 | 27 | void error(int line, std::string_view message) { 28 | report(line, "", message); 29 | } 30 | 31 | void runtimeError(const RuntimeError& error) { 32 | std::cerr << error.what() << 33 | "\n[line " << error.token.line << "]\n"; 34 | hadRuntimeError = true; 35 | } 36 | -------------------------------------------------------------------------------- /chapter9/Lox.cpp: -------------------------------------------------------------------------------- 1 | #include // std::strerror 2 | #include // readFile 3 | #include // std::getline 4 | #include 5 | #include 6 | #include "Error.h" 7 | #include "Interpreter.h" 8 | #include "Parser.h" 9 | #include "Scanner.h" 10 | 11 | std::string readFile(std::string_view path) { 12 | std::ifstream file{path.data(), std::ios::in | std::ios::binary | 13 | std::ios::ate}; 14 | if (!file) { 15 | std::cerr << "Failed to open file " << path << ": " 16 | << std::strerror(errno) << "\n"; 17 | std::exit(74); 18 | }; 19 | 20 | std::string contents; 21 | contents.resize(file.tellg()); 22 | 23 | file.seekg(0, std::ios::beg); 24 | file.read(contents.data(), contents.size()); 25 | 26 | return contents; 27 | } 28 | 29 | Interpreter interpreter{}; 30 | 31 | void run(std::string_view source) { 32 | Scanner scanner {source}; 33 | std::vector tokens = scanner.scanTokens(); 34 | Parser parser{tokens}; 35 | std::vector> statements = parser.parse(); 36 | 37 | // Stop if there was a syntax error. 38 | if (hadError) return; 39 | 40 | // Stop if there was a resolution error. 41 | if (hadError) return; 42 | 43 | interpreter.interpret(statements); 44 | } 45 | 46 | void runFile(std::string_view path) { 47 | std::string contents = readFile(path); 48 | run(contents); 49 | 50 | // Indicate an error in the exit code. 51 | if (hadError) std::exit(65); 52 | if (hadRuntimeError) std::exit(70); 53 | } 54 | 55 | void runPrompt() { 56 | for (;;) { 57 | std::cout << "> "; 58 | std::string line; 59 | if (!std::getline(std::cin, line)) break; 60 | run(line); 61 | hadError = false; 62 | } 63 | } 64 | 65 | int main(int argc, char* argv[]) { 66 | if (argc > 2) { 67 | std::cout << "Usage: jlox [script]\n"; 68 | std::exit(64); 69 | } else if (argc == 2) { 70 | runFile(argv[1]); 71 | } else { 72 | runPrompt(); 73 | } 74 | } -------------------------------------------------------------------------------- /chapter9/Makefile: -------------------------------------------------------------------------------- 1 | CXX := g++ 2 | CXXFLAGS := -ggdb -std=c++17 3 | CPPFLAGS := -MMD 4 | 5 | COMPILE := $(CXX) $(CXXFLAGS) $(CPPFLAGS) 6 | 7 | SRCS := AstPrinterDriver.cpp GenerateAst.cpp Lox.cpp 8 | DEPS := $(SRCS:.cpp=.d) 9 | 10 | 11 | jlox: Expr.h Stmt.h Lox.o 12 | @$(COMPILE) Lox.o -o $@ 13 | 14 | 15 | ast_printer: Expr.h Stmt.h AstPrinterDriver.o 16 | @$(COMPILE) AstPrinterDriver.o -o $@ 17 | 18 | 19 | generate_ast: GenerateAst.o 20 | @$(COMPILE) $< -o $@ 21 | 22 | 23 | Expr.h Stmt.h: generate_ast 24 | @./generate_ast . 25 | 26 | 27 | .PHONY: clean 28 | clean: 29 | rm -f *.d *.o ast_printer generate_ast jlox 30 | 31 | 32 | -include $(DEPS) 33 | 34 | 35 | define make_test 36 | .PHONY: $(1) 37 | $(1): 38 | @make jlox >/dev/null 39 | @echo "testing jlox with $(1).lox ..." 40 | @./jlox tests/$(1).lox | diff -u --color tests/$(1).lox.expected -; 41 | endef 42 | 43 | 44 | TESTS = \ 45 | test-statements \ 46 | test-statements2 \ 47 | test-statements3 \ 48 | test-statements4 \ 49 | test-statements5 \ 50 | test-statements6 \ 51 | test-control-flow \ 52 | test-control-flow2 \ 53 | 54 | 55 | $(foreach test, $(TESTS), $(eval $(call make_test,$(test)))) 56 | 57 | 58 | .PHONY: test-all 59 | test-all: 60 | @for test in $(TESTS); do \ 61 | make -s $$test; \ 62 | done 63 | -------------------------------------------------------------------------------- /chapter9/RuntimeError.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Token.h" 5 | 6 | class RuntimeError: public std::runtime_error { 7 | public: 8 | const Token& token; 9 | 10 | RuntimeError(const Token& token, std::string_view message) 11 | : std::runtime_error{message.data()}, token{token} 12 | {} 13 | }; -------------------------------------------------------------------------------- /chapter9/Token.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include // std::move 6 | #include "TokenType.h" 7 | 8 | class Token { 9 | public: 10 | const TokenType type; 11 | const std::string lexeme; 12 | const std::any literal; 13 | const int line; 14 | 15 | Token(TokenType type, std::string lexeme, std::any literal, 16 | int line) 17 | : type{type}, lexeme{std::move(lexeme)}, 18 | literal{std::move(literal)}, line{line} 19 | {} 20 | 21 | std::string toString() const { 22 | std::string literal_text; 23 | 24 | switch (type) { 25 | case (IDENTIFIER): 26 | literal_text = lexeme; 27 | break; 28 | case (STRING): 29 | literal_text = std::any_cast(literal); 30 | break; 31 | case (NUMBER): 32 | literal_text = std::to_string(std::any_cast(literal)); 33 | break; 34 | case (TRUE): 35 | literal_text = "true"; 36 | break; 37 | case (FALSE): 38 | literal_text = "false"; 39 | break; 40 | default: 41 | literal_text = "nil"; 42 | } 43 | 44 | return ::toString(type) + " " + lexeme + " " + literal_text; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /chapter9/TokenType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | enum TokenType { 6 | // Single-character tokens. 7 | LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, 8 | COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, 9 | 10 | // One or two character tokens. 11 | BANG, BANG_EQUAL, 12 | EQUAL, EQUAL_EQUAL, 13 | GREATER, GREATER_EQUAL, 14 | LESS, LESS_EQUAL, 15 | 16 | // Literals. 17 | IDENTIFIER, STRING, NUMBER, 18 | 19 | // Keywords. 20 | AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR, 21 | PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE, 22 | 23 | END_OF_FILE 24 | }; 25 | 26 | std::string toString(TokenType type) { 27 | static const std::string strings[] = { 28 | "LEFT_PAREN", "RIGHT_PAREN", "LEFT_BRACE", "RIGHT_BRACE", 29 | "COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON", "SLASH", "STAR", 30 | "BANG", "BANG_EQUAL", 31 | "EQUAL", "EQUAL_EQUAL", 32 | "GREATER", "GREATER_EQUAL", 33 | "LESS", "LESS_EQUAL", 34 | "IDENTIFIER", "STRING", "NUMBER", 35 | "AND", "CLASS", "ELSE", "FALSE", "FUN", "FOR", "IF", "NIL", "OR", 36 | "PRINT", "RETURN", "SUPER", "THIS", "TRUE", "VAR", "WHILE", 37 | "END_OF_FILE" 38 | }; 39 | 40 | return strings[static_cast(type)]; 41 | } 42 | -------------------------------------------------------------------------------- /chapter9/tests/test-control-flow.lox: -------------------------------------------------------------------------------- 1 | print "hi" or 2; // "hi". 2 | print nil or "yes"; // "yes". 3 | -------------------------------------------------------------------------------- /chapter9/tests/test-control-flow.lox.expected: -------------------------------------------------------------------------------- 1 | hi 2 | yes 3 | -------------------------------------------------------------------------------- /chapter9/tests/test-control-flow2.lox: -------------------------------------------------------------------------------- 1 | var a = 0; 2 | var temp; 3 | 4 | for (var b = 1; a < 10000; b = temp + b) { 5 | print a; 6 | temp = a; 7 | a = b; 8 | } 9 | -------------------------------------------------------------------------------- /chapter9/tests/test-control-flow2.lox.expected: -------------------------------------------------------------------------------- 1 | 0.000000 2 | 1.000000 3 | 1.000000 4 | 2.000000 5 | 3.000000 6 | 5.000000 7 | 8.000000 8 | 13.000000 9 | 21.000000 10 | 34.000000 11 | 55.000000 12 | 89.000000 13 | 144.000000 14 | 233.000000 15 | 377.000000 16 | 610.000000 17 | 987.000000 18 | 1597.000000 19 | 2584.000000 20 | 4181.000000 21 | 6765.000000 22 | -------------------------------------------------------------------------------- /chapter9/tests/test-statements.lox: -------------------------------------------------------------------------------- 1 | print "one"; 2 | print true; 3 | print 2 + 1; 4 | -------------------------------------------------------------------------------- /chapter9/tests/test-statements.lox.expected: -------------------------------------------------------------------------------- 1 | one 2 | true 3 | 3.000000 4 | -------------------------------------------------------------------------------- /chapter9/tests/test-statements2.lox: -------------------------------------------------------------------------------- 1 | var a = "before"; 2 | print a; // "before". 3 | var a = "after"; 4 | print a; // "after". 5 | -------------------------------------------------------------------------------- /chapter9/tests/test-statements2.lox.expected: -------------------------------------------------------------------------------- 1 | before 2 | after 3 | -------------------------------------------------------------------------------- /chapter9/tests/test-statements3.lox: -------------------------------------------------------------------------------- 1 | var a; 2 | print a; // "nil". 3 | -------------------------------------------------------------------------------- /chapter9/tests/test-statements3.lox.expected: -------------------------------------------------------------------------------- 1 | nil 2 | -------------------------------------------------------------------------------- /chapter9/tests/test-statements4.lox: -------------------------------------------------------------------------------- 1 | var a = 1; 2 | var b = 2; 3 | print a + b; 4 | -------------------------------------------------------------------------------- /chapter9/tests/test-statements4.lox.expected: -------------------------------------------------------------------------------- 1 | 3.000000 2 | -------------------------------------------------------------------------------- /chapter9/tests/test-statements5.lox: -------------------------------------------------------------------------------- 1 | var a = 1; 2 | print a = 2; // "2". 3 | -------------------------------------------------------------------------------- /chapter9/tests/test-statements5.lox.expected: -------------------------------------------------------------------------------- 1 | 2.000000 2 | -------------------------------------------------------------------------------- /chapter9/tests/test-statements6.lox: -------------------------------------------------------------------------------- 1 | var a = "global a"; 2 | var b = "global b"; 3 | var c = "global c"; 4 | { 5 | var a = "outer a"; 6 | var b = "outer b"; 7 | { 8 | var a = "inner a"; 9 | print a; 10 | print b; 11 | print c; 12 | } 13 | print a; 14 | print b; 15 | print c; 16 | } 17 | print a; 18 | print b; 19 | print c; 20 | -------------------------------------------------------------------------------- /chapter9/tests/test-statements6.lox.expected: -------------------------------------------------------------------------------- 1 | inner a 2 | outer b 3 | global c 4 | outer a 5 | outer b 6 | global c 7 | global a 8 | global b 9 | global c 10 | --------------------------------------------------------------------------------