🏗️ make interpreter more testable.
parent
c450c89add
commit
91a7f61c42
|
@ -23,6 +23,8 @@ snake_lib = static_library('snake',
|
||||||
'src/State.cpp',
|
'src/State.cpp',
|
||||||
'src/DAG.cpp',
|
'src/DAG.cpp',
|
||||||
'src/SymTable.cpp',
|
'src/SymTable.cpp',
|
||||||
|
'src/Loader.cpp',
|
||||||
|
'src/Executor.cpp',
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
dependency('sqlite3')
|
dependency('sqlite3')
|
||||||
|
@ -46,6 +48,7 @@ executable('snake-tests',
|
||||||
'tests/main.cpp',
|
'tests/main.cpp',
|
||||||
'tests/Lexer.cpp',
|
'tests/Lexer.cpp',
|
||||||
'tests/Parser.cpp',
|
'tests/Parser.cpp',
|
||||||
|
'tests/Interpreter.cpp',
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
snake_dep,
|
snake_dep,
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -5,7 +5,12 @@
|
||||||
|
|
||||||
namespace sn
|
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
|
// Process the document ast
|
||||||
// ------------------------
|
// ------------------------
|
||||||
auto node = parse_snakefile();
|
auto node = m_loader->parse_snakefile();
|
||||||
process(node);
|
process(node);
|
||||||
|
|
||||||
// Get all files
|
// Get all files
|
||||||
|
@ -47,11 +52,9 @@ namespace sn
|
||||||
|
|
||||||
// Get modified files
|
// Get modified files
|
||||||
// ------------------
|
// ------------------
|
||||||
auto state = std::make_shared<State>();
|
m_state->init(files);
|
||||||
|
|
||||||
state->init(files);
|
auto modified_files = m_state->get_modified_files(files);
|
||||||
|
|
||||||
auto modified_files = state->get_modified_files(files);
|
|
||||||
|
|
||||||
// Build DAG
|
// Build DAG
|
||||||
// ---------
|
// ---------
|
||||||
|
@ -120,7 +123,7 @@ namespace sn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state->update(file);
|
m_state->update(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sorted.empty())
|
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)
|
std::string Interpreter::get_value(std::shared_ptr<Node> node)
|
||||||
{
|
{
|
||||||
if (node->type() == NODE_IDENT)
|
if (node->type() == NODE_IDENT)
|
||||||
|
@ -283,20 +254,7 @@ namespace sn
|
||||||
|
|
||||||
std::string Interpreter::execute(std::string const& script) const
|
std::string Interpreter::execute(std::string const& script) const
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
return m_executor->execute(script);
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::filesystem::path
|
std::filesystem::path
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
#include "State.hpp"
|
#include "State.hpp"
|
||||||
#include "DAG.hpp"
|
#include "DAG.hpp"
|
||||||
#include "SymTable.hpp"
|
#include "SymTable.hpp"
|
||||||
|
#include "Loader.hpp"
|
||||||
|
#include "Executor.hpp"
|
||||||
|
|
||||||
namespace sn
|
namespace sn
|
||||||
{
|
{
|
||||||
|
@ -19,16 +21,18 @@ namespace sn
|
||||||
class Interpreter
|
class Interpreter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit Interpreter();
|
explicit Interpreter(std::shared_ptr<State> state,
|
||||||
|
std::shared_ptr<Loader> loader,
|
||||||
|
std::shared_ptr<Executor> executor);
|
||||||
virtual ~Interpreter();
|
virtual ~Interpreter();
|
||||||
|
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
std::filesystem::path find_snakefile();
|
|
||||||
std::string load_snakefile();
|
|
||||||
std::shared_ptr<Node> parse_snakefile();
|
|
||||||
|
|
||||||
private:
|
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::unordered_map<std::filesystem::path,
|
||||||
std::vector<std::filesystem::path>> m_dependencies;
|
std::vector<std::filesystem::path>> m_dependencies;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -22,10 +22,10 @@ namespace sn
|
||||||
explicit State();
|
explicit State();
|
||||||
virtual ~State();
|
virtual ~State();
|
||||||
|
|
||||||
void init(std::vector<std::filesystem::path> const& files);
|
virtual void init(std::vector<std::filesystem::path> const& files);
|
||||||
void update(std::filesystem::path path);
|
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);
|
get_modified_files(std::vector<std::filesystem::path> const& files);
|
||||||
|
|
||||||
std::filesystem::path format_path(std::filesystem::path path) const;
|
std::filesystem::path format_path(std::filesystem::path path) const;
|
||||||
|
|
|
@ -3,10 +3,15 @@
|
||||||
#include "snake.hpp"
|
#include "snake.hpp"
|
||||||
#include "Interpreter.hpp"
|
#include "Interpreter.hpp"
|
||||||
#include "State.hpp"
|
#include "State.hpp"
|
||||||
|
#include "Executor.hpp"
|
||||||
|
|
||||||
int main(int, char**)
|
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();
|
interpreter.run();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
Loading…
Reference in New Issue