🏗️ make interpreter more testable.

main
bog 2023-10-14 15:34:58 +02:00
parent c450c89add
commit 91a7f61c42
10 changed files with 261 additions and 62 deletions

View File

@ -23,6 +23,8 @@ snake_lib = static_library('snake',
'src/State.cpp',
'src/DAG.cpp',
'src/SymTable.cpp',
'src/Loader.cpp',
'src/Executor.cpp',
],
dependencies: [
dependency('sqlite3')
@ -46,6 +48,7 @@ executable('snake-tests',
'tests/main.cpp',
'tests/Lexer.cpp',
'tests/Parser.cpp',
'tests/Interpreter.cpp',
],
dependencies: [
snake_dep,

30
src/Executor.cpp Normal file
View File

@ -0,0 +1,30 @@
#include "Executor.hpp"
namespace sn
{
/*explicit*/ Executor::Executor()
{
}
/*virtual*/ Executor::~Executor()
{
}
/*virtual*/ std::string Executor::execute(std::string const& command)
{
std::stringstream ss;
FILE* out = popen(command.c_str(), "r");
assert(out);
char buf;
while ( fread(&buf, sizeof(char), 1, out) )
{
ss << buf;
}
pclose(out);
return ss.str();
}
}

20
src/Executor.hpp Normal file
View File

@ -0,0 +1,20 @@
#ifndef sn_EXECUTOR_HPP
#define sn_EXECUTOR_HPP
#include "commons.hpp"
namespace sn
{
class Executor
{
public:
explicit Executor();
virtual ~Executor();
virtual std::string execute(std::string const& command);
private:
};
}
#endif

View File

@ -5,7 +5,12 @@
namespace sn
{
/*explicit*/ Interpreter::Interpreter()
/*explicit*/ Interpreter::Interpreter(std::shared_ptr<State> state,
std::shared_ptr<Loader> loader,
std::shared_ptr<Executor> executor)
: m_state { state }
, m_loader { loader }
, m_executor { executor }
{
}
@ -17,7 +22,7 @@ namespace sn
{
// Process the document ast
// ------------------------
auto node = parse_snakefile();
auto node = m_loader->parse_snakefile();
process(node);
// Get all files
@ -47,11 +52,9 @@ namespace sn
// Get modified files
// ------------------
auto state = std::make_shared<State>();
m_state->init(files);
state->init(files);
auto modified_files = state->get_modified_files(files);
auto modified_files = m_state->get_modified_files(files);
// Build DAG
// ---------
@ -120,7 +123,7 @@ namespace sn
}
}
state->update(file);
m_state->update(file);
}
if (sorted.empty())
@ -129,38 +132,6 @@ namespace sn
}
}
std::filesystem::path Interpreter::find_snakefile()
{
auto path = std::filesystem::current_path() / "Snakefile";
if (!std::filesystem::exists(path))
{
throw run_error {"Snakefile not found"};
}
return path;
}
std::string Interpreter::load_snakefile()
{
std::ifstream file {find_snakefile()};
assert(file);
std::stringstream ss;
ss << file.rdbuf();
return ss.str();
}
std::shared_ptr<Node> Interpreter::parse_snakefile()
{
Lexer lexer;
lexer.scan(load_snakefile());
Parser parser;
auto node = parser.parse(lexer.all());
return node;
}
std::string Interpreter::get_value(std::shared_ptr<Node> node)
{
if (node->type() == NODE_IDENT)
@ -283,20 +254,7 @@ namespace sn
std::string Interpreter::execute(std::string const& script) const
{
std::stringstream ss;
FILE* out = popen(script.c_str(), "r");
assert(out);
char buf;
while ( fread(&buf, sizeof(char), 1, out) )
{
ss << buf;
}
pclose(out);
return ss.str();
return m_executor->execute(script);
}
std::filesystem::path

View File

@ -6,6 +6,8 @@
#include "State.hpp"
#include "DAG.hpp"
#include "SymTable.hpp"
#include "Loader.hpp"
#include "Executor.hpp"
namespace sn
{
@ -19,16 +21,18 @@ namespace sn
class Interpreter
{
public:
explicit Interpreter();
explicit Interpreter(std::shared_ptr<State> state,
std::shared_ptr<Loader> loader,
std::shared_ptr<Executor> executor);
virtual ~Interpreter();
void run();
std::filesystem::path find_snakefile();
std::string load_snakefile();
std::shared_ptr<Node> parse_snakefile();
private:
std::shared_ptr<State> m_state;
std::shared_ptr<Loader> m_loader;
std::shared_ptr<Executor> m_executor;
std::unordered_map<std::filesystem::path,
std::vector<std::filesystem::path>> m_dependencies;

46
src/Loader.cpp Normal file
View File

@ -0,0 +1,46 @@
#include "Loader.hpp"
#include "Lexer.hpp"
#include "Parser.hpp"
namespace sn
{
/*explicit*/ Loader::Loader()
{
}
/*virtual*/ Loader::~Loader()
{
}
std::filesystem::path Loader::find_snakefile()
{
auto path = std::filesystem::current_path() / "Snakefile";
if (!std::filesystem::exists(path))
{
throw load_error {"Snakefile not found"};
}
return path;
}
std::string Loader::load_snakefile()
{
std::ifstream file {find_snakefile()};
assert(file);
std::stringstream ss;
ss << file.rdbuf();
return ss.str();
}
std::shared_ptr<Node> Loader::parse_snakefile()
{
Lexer lexer;
lexer.scan(load_snakefile());
Parser parser;
auto node = parser.parse(lexer.all());
return node;
}
}

30
src/Loader.hpp Normal file
View File

@ -0,0 +1,30 @@
#ifndef sn_LOADER_HPP
#define sn_LOADER_HPP
#include "commons.hpp"
#include "Node.hpp"
namespace sn
{
SN_ERROR(load_error);
/**
* Find and compile snakefiles.
* @see Lexer
* @see Parser
**/
class Loader
{
public:
explicit Loader();
virtual ~Loader();
virtual std::filesystem::path find_snakefile();
virtual std::string load_snakefile();
virtual std::shared_ptr<Node> parse_snakefile();
private:
};
}
#endif

View File

@ -22,10 +22,10 @@ namespace sn
explicit State();
virtual ~State();
void init(std::vector<std::filesystem::path> const& files);
void update(std::filesystem::path path);
virtual void init(std::vector<std::filesystem::path> const& files);
virtual void update(std::filesystem::path path);
std::vector<std::filesystem::path>
virtual std::vector<std::filesystem::path>
get_modified_files(std::vector<std::filesystem::path> const& files);
std::filesystem::path format_path(std::filesystem::path path) const;

View File

@ -3,10 +3,15 @@
#include "snake.hpp"
#include "Interpreter.hpp"
#include "State.hpp"
#include "Executor.hpp"
int main(int, char**)
{
sn::Interpreter interpreter;
auto state = std::make_shared<sn::State>();
auto loader = std::make_shared<sn::Loader>();
auto executor = std::make_shared<sn::Executor>();
sn::Interpreter interpreter {state, loader, executor};
interpreter.run();
return 0;

103
tests/Interpreter.cpp Normal file
View File

@ -0,0 +1,103 @@
#include <catch2/catch.hpp>
#include "../src/Interpreter.hpp"
#define TEST_RUN(SRC, ...) \
test_run(SRC, {__VA_ARGS__})
using namespace sn;
struct StateMock: public State {
std::vector<std::filesystem::path> modified_files;
StateMock(std::vector<std::filesystem::path> const& p_modified_files)
: modified_files { p_modified_files }
{
}
virtual void init(std::vector<std::filesystem::path> const&) override
{
}
virtual void update(std::filesystem::path) override
{
}
virtual std::vector<std::filesystem::path>
get_modified_files(std::vector<std::filesystem::path> const&) override
{
return modified_files;
}
};
struct LoaderMock: public Loader {
std::string snakefile;
LoaderMock(std::string const& p_snakefile)
: snakefile { p_snakefile }
{
}
virtual std::filesystem::path find_snakefile() override { return ""; }
virtual std::string load_snakefile() override
{
return snakefile;
}
};
struct ExecutorMock: public Executor {
std::vector<std::string> history;
virtual std::string execute(std::string const& command) override
{
history.push_back(command);
return "";
}
};
class InterpreterTest
{
public:
explicit InterpreterTest() {}
virtual ~InterpreterTest() {}
std::vector<std::string>
test_run(std::string const& snakefile,
std::vector<std::filesystem::path> const& modified)
{
auto state = std::make_shared<StateMock>(modified);
auto loader = std::make_shared<LoaderMock>(snakefile);
auto executor = std::make_shared<ExecutorMock>();
Interpreter interpreter {state, loader, executor};
interpreter.run();
return executor->history;
}
void test_contains(std::string const& str, std::vector<std::string> vec)
{
INFO("'" << str << "' not found");
REQUIRE(std::find(std::begin(vec), std::end(vec), str)
!= std::end(vec));
}
protected:
};
TEST_CASE_METHOD(InterpreterTest, "Interpreter_simple_var")
{
auto modified = std::vector<std::filesystem::path> {
std::filesystem::path("a"),
};
std::stringstream ss;
ss << "$X = a" << std::endl;
ss << "b -> $X {" << std::endl;
ss << "executed" << std::endl;
ss << "}" << std::endl;
auto res = TEST_RUN(ss.str(), std::filesystem::path("a"));
test_contains("executed", res);
}