Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Daan Posthuma authored and Daan Posthuma committed Dec 14, 2023
1 parent da74eca commit 362f44b
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 132 deletions.
13 changes: 7 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
# CMakeList.txt : Top-level CMake project file, do global configuration
# and include sub-projects here.
#
cmake_minimum_required (VERSION 3.8)
cmake_minimum_required (VERSION 3.8)

project ("lox")

# Enable Hot Reload for MSVC compilers if supported.
if (POLICY CMP0141)
cmake_policy(SET CMP0141 NEW)
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<IF:$<AND:$<C_COMPILER_ID:MSVC>,$<CXX_COMPILER_ID:MSVC>>,$<$<CONFIG:Debug,RelWithDebInfo>:EditAndContinue>,$<$<CONFIG:Debug,RelWithDebInfo>:ProgramDatabase>>")
endif()

project ("lox")
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# Include sub-projects.
add_subdirectory ("lox")

24 changes: 20 additions & 4 deletions lox/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
add_executable (lox "Expr.cpp" "Environment.cpp" "ExprToString.cpp" "Interpreter.cpp" "Lox.cpp" "Object.cpp" "Parser.cpp" "Scanner.cpp" "Stmt.cpp" "Token.cpp" "TokenType.cpp" "LoxCallable.cpp" "Resolver.cpp")
add_library(loxlib
Environment.cpp
Expr.cpp
ExprToString.cpp
Interpreter.cpp
Lox.cpp
LoxCallable.cpp
Object.cpp
Parser.cpp
Resolver.cpp
Scanner.cpp
Stmt.cpp
Token.cpp
TokenType.cpp
)

if (CMAKE_VERSION VERSION_GREATER 3.25)
set_property(TARGET lox PROPERTY CXX_STANDARD 26)
endif()
add_executable (lox Main.cpp)

target_link_libraries(lox loxlib)

add_subdirectory ("tests")
122 changes: 1 addition & 121 deletions lox/Lox.cpp
Original file line number Diff line number Diff line change
@@ -1,21 +1,9 @@
#include "Scanner.h"
#include "Token.h"
#include "Token.h"
#include "TokenType.h"
#include "Lox.h"
#include "Parser.h"
#include "ExprToString.h"
#include "Interpreter.h"
#include "Environment.h"
#include "LoxCallable.h"
#include "Resolver.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <string>
#include <cassert>
#include <algorithm>
#include <chrono>

using namespace std::string_literals;

Expand All @@ -40,111 +28,3 @@ void Lox::error(Token const& token, std::string const& message) {
bool Lox::hadError = false;
bool Lox::debugEnabled = false;
Environment Lox::globals = {};
//Environment Lox::environment = globals;

namespace {

void addNativeFunctionsToGlobalEnvironment() {
Lox::globals.define("clock", LoxCallable([](std::vector<Object> const&) {
return static_cast<double>(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
}, 0, "clock (native)"));

Lox::globals.define("monkey", LoxCallable([](std::vector<Object> const&) {
return " __ \n w c(..)o ( \n \\__(-) __) \n /\\ ( \n /(_)___) \n w /| \n | \\ \n m m "s;
}, 0, "monkey (native)"));

Lox::globals.define("readString", LoxCallable([](std::vector<Object> const&) {
std::string s;
std::cin >> s;
return s;
}, 0, "readString (native)"));

Lox::globals.define("subString", LoxCallable([](std::vector<Object> const& arguments) {
auto const string = static_cast<std::string>(arguments[0]);
auto const offd = static_cast<double>(arguments[1]);
auto const countd = static_cast<double>(arguments[2]);
auto const off = static_cast<int>(offd);
auto const count = static_cast<int>(countd);
return string.substr(off, count);
}, 3, "subString (native)"));

}

void run(std::string source) {

auto const tScanTokensStart = std::chrono::high_resolution_clock::now();
auto const tokens = scanTokens(source);
auto const tScanTokensEnd = std::chrono::high_resolution_clock::now();

if (Lox::debugEnabled) {
std::cout << "Tokens: ";
std::ranges::for_each(tokens, [first = true](Token const& token) mutable { std::cout << (first ? "" : ", ") << "[" << token.toString() << "]"; first = false; });
std::cout << std::endl;
}

auto const tParseStart = std::chrono::high_resolution_clock::now();
auto const statements = parse(tokens);
auto const tParseEnd = std::chrono::high_resolution_clock::now();

if (Lox::debugEnabled) {
std::cout << "Num statements: " << statements.size() << std::endl;
}

auto const tResolveStart = std::chrono::high_resolution_clock::now();
auto const locals = Lox::hadError ? ResolvedLocals() : resolve(statements);
auto const tResolveEnd = std::chrono::high_resolution_clock::now();

auto const tInterpretStart = std::chrono::high_resolution_clock::now();
auto const result = Lox::hadError ? Object{} : interpret(statements, locals);
auto const tInterpretEnd = std::chrono::high_resolution_clock::now();

if (!result.isNil()) {
std::cout << result.toString() << std::endl;
}

if (Lox::debugEnabled) {
std::cout << "Scanner: " << std::chrono::duration_cast<std::chrono::microseconds>(tScanTokensEnd - tScanTokensStart) << std::endl;
std::cout << "Parser: " << std::chrono::duration_cast<std::chrono::microseconds>(tParseEnd - tParseStart) << std::endl;
std::cout << "Resolver: " << std::chrono::duration_cast<std::chrono::microseconds>(tResolveEnd - tResolveStart) << std::endl;
std::cout << "Interpreter: " << std::chrono::duration_cast<std::chrono::microseconds>(tInterpretEnd - tInterpretStart) << std::endl;
}
}

void runFile(std::string fileName) {
std::ifstream t(fileName);
std::stringstream buffer;
buffer << t.rdbuf();
run(buffer.str());
}

void runPrompt() {
while (true) {
std::cout << "> ";
std::string line;
getline(std::cin, line);
if (line == "exit" || line == "q") return;
run(line);
Lox::hadError = false;
}
}
}

int main(int argc, char* argv[])
{
std::cout << std::boolalpha;

addNativeFunctionsToGlobalEnvironment();

if (argc > 2) {
std::cerr << "Usage: lox [script]" << std::endl;
return EXIT_FAILURE;
}
else if (argc == 2) {
runFile(argv[1]);
}
else {
runPrompt();
}

return 0;
}
121 changes: 121 additions & 0 deletions lox/Main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#include "Scanner.h"
#include "Token.h"
#include "Lox.h"
#include "Parser.h"
#include "Interpreter.h"
#include "Environment.h"
#include "LoxCallable.h"
#include "Resolver.h"
#include <iostream>
#include <fstream>
#include <algorithm>
#include <chrono>

using namespace std::string_literals;

namespace {

void addNativeFunctionsToGlobalEnvironment() {
Lox::globals.define("clock", LoxCallable([](std::vector<Object> const&) {
return static_cast<double>(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
}, 0, "clock (native)"));

Lox::globals.define("monkey", LoxCallable([](std::vector<Object> const&) {
return " __ \n w c(..)o ( \n \\__(-) __) \n /\\ ( \n /(_)___) \n w /| \n | \\ \n m m "s;
}, 0, "monkey (native)"));

Lox::globals.define("readString", LoxCallable([](std::vector<Object> const&) {
std::string s;
std::cin >> s;
return s;
}, 0, "readString (native)"));

Lox::globals.define("subString", LoxCallable([](std::vector<Object> const& arguments) {
auto const string = static_cast<std::string>(arguments[0]);
auto const offd = static_cast<double>(arguments[1]);
auto const countd = static_cast<double>(arguments[2]);
auto const off = static_cast<int>(offd);
auto const count = static_cast<int>(countd);
return string.substr(off, count);
}, 3, "subString (native)"));

}

void run(std::string source) {

auto const tScanTokensStart = std::chrono::high_resolution_clock::now();
auto const tokens = scanTokens(source);
auto const tScanTokensEnd = std::chrono::high_resolution_clock::now();

if (Lox::debugEnabled) {
std::cout << "Tokens: ";
std::ranges::for_each(tokens, [first = true](Token const& token) mutable { std::cout << (first ? "" : ", ") << "[" << token.toString() << "]"; first = false; });
std::cout << std::endl;
}

auto const tParseStart = std::chrono::high_resolution_clock::now();
auto const statements = parse(tokens);
auto const tParseEnd = std::chrono::high_resolution_clock::now();

if (Lox::debugEnabled) {
std::cout << "Num statements: " << statements.size() << std::endl;
}

auto const tResolveStart = std::chrono::high_resolution_clock::now();
auto const locals = Lox::hadError ? ResolvedLocals() : resolve(statements);
auto const tResolveEnd = std::chrono::high_resolution_clock::now();

auto const tInterpretStart = std::chrono::high_resolution_clock::now();
auto const result = Lox::hadError ? Object{} : interpret(statements, locals);
auto const tInterpretEnd = std::chrono::high_resolution_clock::now();

if (!result.isNil()) {
std::cout << result.toString() << std::endl;
}

if (Lox::debugEnabled) {
std::cout << "Scanner: " << std::chrono::duration_cast<std::chrono::microseconds>(tScanTokensEnd - tScanTokensStart) << std::endl;
std::cout << "Parser: " << std::chrono::duration_cast<std::chrono::microseconds>(tParseEnd - tParseStart) << std::endl;
std::cout << "Resolver: " << std::chrono::duration_cast<std::chrono::microseconds>(tResolveEnd - tResolveStart) << std::endl;
std::cout << "Interpreter: " << std::chrono::duration_cast<std::chrono::microseconds>(tInterpretEnd - tInterpretStart) << std::endl;
}
}

void runFile(std::string fileName) {
std::ifstream t(fileName);
std::stringstream buffer;
buffer << t.rdbuf();
run(buffer.str());
}

void runPrompt() {
while (true) {
std::cout << "> ";
std::string line;
getline(std::cin, line);
if (line == "exit" || line == "q") return;
run(line);
Lox::hadError = false;
}
}
}

int main(int argc, char* argv[])
{
std::cout << std::boolalpha;

addNativeFunctionsToGlobalEnvironment();

if (argc > 2) {
std::cerr << "Usage: lox [script]" << std::endl;
return EXIT_FAILURE;
}
else if (argc == 2) {
runFile(argv[1]);
}
else {
runPrompt();
}

return 0;
}
2 changes: 1 addition & 1 deletion lox/Resolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ namespace {
// Statements:
void resolveVarStmt(VarStmt const& stmt, ResolverContext& context) {
declare(stmt.name(), context.scopes);
if (!stmt.initializer()) {
if (stmt.initializer()) {
resolve(*stmt.initializer(), context);
}
define(stmt.name(), context.scopes);
Expand Down
12 changes: 12 additions & 0 deletions lox/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
include(FetchContent)
FetchContent_Declare(
catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.5.0
)
FetchContent_MakeAvailable(Catch2)

include_directories(..)

add_executable(tests TestScanner.cpp TestParser.cpp TestResolver.cpp)
target_link_libraries(tests PRIVATE Catch2::Catch2WithMain loxlib)
36 changes: 36 additions & 0 deletions lox/tests/TestParser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include "Token.h"
#include "TokenType.h"
#include "Object.h"
#include "Parser.h"
#include "Stmt.h"
#include "Lox.h"
#include <catch2/catch_test_macros.hpp>

namespace lox::parser {

auto const tSEMICOLON = Token(TokenType::SEMICOLON, ";", Object(), 0);
auto const tEND_OF_FILE = Token(TokenType::END_OF_FILE, "", Object(), 0);

auto const tPRINT = Token(TokenType::PRINT, "print", Object(), 0);
auto const tNUMBER = Token(TokenType::NUMBER, "3", Object(3.0), 0);

auto const tVAR = Token(TokenType::VAR, "print", Object(), 0);
auto const tIDENTIFIER = Token(TokenType::IDENTIFIER, "test", Object(), 0);
auto const tEQUAL = Token(TokenType::EQUAL, "=", Object(), 0);
auto const tSTRING = Token(TokenType::STRING, "Test", Object("Test"), 0);

TEST_CASE("Parser produces print statement", "[lox.parser.print]") {
auto const printNumber = parse({ tPRINT, tNUMBER, tSEMICOLON, tEND_OF_FILE });
REQUIRE(!Lox::hadError);
REQUIRE(printNumber.size() == 1);
REQUIRE(dynamic_cast<PrintStmt const*>(printNumber.front()));
}

TEST_CASE("Parser produces var statement", "[lox.parser.var]") {
auto const printNumber = parse({ tVAR, tIDENTIFIER, tEQUAL, tSTRING, tSEMICOLON, tEND_OF_FILE });
REQUIRE(!Lox::hadError);
REQUIRE(printNumber.size() == 1);
REQUIRE(dynamic_cast<VarStmt const*>(printNumber.front()));
}

}
Loading

0 comments on commit 362f44b

Please sign in to comment.