can now build rules.

main
bog 2023-10-14 03:56:31 +02:00
parent 01446e331b
commit e862da9000
19 changed files with 1400 additions and 4 deletions

View File

@ -7,7 +7,8 @@ None for now, but maybe one day...
## How to install
Snake relies on some libraries you must install in order to compile it.
- catch2 (for testing)
- catch2 (only for testing)
- sqlite3
First, you need to compile Snake using the given Makefile:
@ -40,4 +41,4 @@ Don't.
## License
Snake is released under the GPLv3.
See the LICENSE file at the project's root for more information.
See the LICENSE file in the project directory for more information.

7
doc/grammar.bnf Normal file
View File

@ -0,0 +1,7 @@
DOC ::= RULE*
RULE ::= TARGET rarrow DEPS BLOCK
TARGET ::= ident+
DEPS ::= ident*
BLOCK ::= obrace CMD_LST cbrace
CMD_LST ::= (CMD (comma CMD)* comma?)?
CMD ::= ident*

View File

@ -16,8 +16,15 @@ configure_file(input: 'src/snake.in.hpp',
snake_lib = static_library('snake',
sources: [
'src/Node.cpp',
'src/Lexer.cpp',
'src/Parser.cpp',
'src/Interpreter.cpp',
'src/State.cpp',
'src/DAG.cpp',
],
dependencies: [
dependency('sqlite3')
])
snake_dep = declare_dependency(link_with: [
@ -36,6 +43,8 @@ executable('snake',
executable('snake-tests',
sources: [
'tests/main.cpp',
'tests/Lexer.cpp',
'tests/Parser.cpp',
],
dependencies: [
snake_dep,

92
src/DAG.cpp Normal file
View File

@ -0,0 +1,92 @@
#include "DAG.hpp"
namespace sn
{
/*explicit*/ DAG::DAG()
{
}
/*virtual*/ DAG::~DAG()
{
}
void DAG::add_node(std::filesystem::path const& path)
{
m_nodes[path] = {};
}
void DAG::link(std::filesystem::path const& lhs,
std::filesystem::path const& rhs)
{
m_nodes[lhs].push_back(rhs);
}
bool DAG::exists(std::filesystem::path const& path) const
{
return m_nodes.find(path) != std::end(m_nodes);
}
std::string DAG::string() const
{
std::stringstream ss;
for (auto entry: m_nodes)
{
ss << entry.first << std::endl;
for (auto d: entry.second)
{
ss << "\t" << d << std::endl;
}
}
return ss.str();
}
std::vector<std::filesystem::path> DAG::sort() const
{
if (m_nodes.empty())
{
return {};
}
std::vector<std::filesystem::path> result;
std::unordered_map<std::filesystem::path, bool> visited;
for (auto const& entry: m_nodes)
{
visited[entry.first] = false;
}
std::function<void(std::filesystem::path)>
visit = [&](auto node){
if (visited[node]) { return; }
for (auto const& entry: m_nodes.at(node))
{
visit(entry);
}
visited[node] = true;
result.insert(std::begin(result), node);
};
bool running = true;
while (running)
{
running = false;
for (auto const& entry: visited)
{
if (!entry.second)
{
running = true;
visit(entry.first);
}
}
}
return result;
}
}

34
src/DAG.hpp Normal file
View File

@ -0,0 +1,34 @@
#ifndef sn_DAG_HPP
#define sn_DAG_HPP
#include "commons.hpp"
namespace sn
{
/**
* Mathematical Directed Acyclic Graph (DAG).
**/
class DAG
{
public:
explicit DAG();
virtual ~DAG();
void add_node(std::filesystem::path const& path);
void link(std::filesystem::path const& lhs,
std::filesystem::path const& rhs);
bool exists(std::filesystem::path const& path) const;
std::string string() const;
std::vector<std::filesystem::path> sort() const;
private:
std::unordered_map<std::filesystem::path,
std::vector<std::filesystem::path>> m_nodes;
};
}
#endif

279
src/Interpreter.cpp Normal file
View File

@ -0,0 +1,279 @@
#include "Interpreter.hpp"
#include "Lexer.hpp"
#include "Parser.hpp"
#include "State.hpp"
namespace sn
{
/*explicit*/ Interpreter::Interpreter()
{
}
/*virtual*/ Interpreter::~Interpreter()
{
}
void Interpreter::run()
{
// Process the document ast
// ------------------------
auto node = parse_snakefile();
process(node);
// Get all files
// -------------
std::vector<std::filesystem::path> files;
for (auto entry: m_dependencies)
{
if (auto itr = std::find(std::begin(files), std::end(files),
entry.first);
itr == std::end(files))
{
auto path = format_path(entry.first);
files.push_back(path);
}
for (auto d: entry.second)
{
if (auto itr = std::find(std::begin(files), std::end(files), d);
itr == std::end(files))
{
files.push_back(d);
}
}
}
// Get modified files
// ------------------
auto state = std::make_shared<State>();
state->init(files);
auto modified_files = state->get_modified_files(files);
// Build DAG
// ---------
auto dag = std::make_shared<DAG>();
std::function<void(std::filesystem::path const&)>
add = [&](std::filesystem::path file) {
file = format_path(file);
if (!dag->exists(file))
{
dag->add_node(file);
}
auto nexts = targets(file);
for (auto const& next: nexts)
{
if (!dag->exists(next))
{
add(next);
}
dag->link(file, next);
}
};
for (auto file: modified_files)
{
add(file);
}
auto sorted = dag->sort();
int max_w = 0;
for (auto file: sorted)
{
max_w = std::fmax(max_w, file.filename().string().size());
}
for (auto file: sorted)
{
if (auto all_scripts=m_scripts.find(file);
all_scripts != std::end(m_scripts))
{
for (auto script: all_scripts->second)
{
int const SQUARES_W = 2;
int const SPACE = 2;
int const WIDTH = max_w + SQUARES_W + SPACE;
std::cout << std::setw(WIDTH);
std::cout << std::left;
std::cout << "[" + file.filename().string() + "]";
std::cout << std::setw(WIDTH);
std::cout << script << std::endl;
auto ret = execute(script);
if (ret.empty() == false)
{
std::cout << ret << std::endl;
}
}
}
state->update(file);
}
if (sorted.empty())
{
std::cout << "everything is up to date" << std::endl;
}
}
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::vector<std::filesystem::path>
Interpreter::targets(std::filesystem::path path) const
{
std::vector<std::filesystem::path> result;
for (auto entry: m_dependencies)
{
if (auto itr = std::find(std::begin(entry.second),
std::end(entry.second),
path);
itr != std::end(entry.second))
{
result.push_back(format_path(entry.first));
}
}
return result;
}
void Interpreter::process(std::shared_ptr<Node> node)
{
switch (node->type())
{
case NODE_DOC: {
for (size_t i=0; i<node->size(); i++)
{
process(*node->get(i));
}
} break;
case NODE_RULE: {
auto all_targets = *node->get(0);
auto deps = *node->get(1);
auto block = *node->get(2);
for (size_t i=0; i<all_targets->size(); i++)
{
auto target = *all_targets->get(i);
for (size_t j=0; j<deps->size(); j++)
{
auto dep = *deps->get(j);
auto path = format_path(dep->repr());
m_dependencies[format_path(target->repr())].push_back(path);
auto target_path = format_path(target->repr());
m_scripts[target_path] = scripts(block);
}
}
} break;
default:
std::cerr << "Unknown node of type '"
<< SN_GET(NodeTypeStr, node->type(), "NODE_")
<< "'"
<< std::endl;
abort();
}
}
std::vector<std::string>
Interpreter::scripts(std::shared_ptr<Node> block) const
{
std::vector<std::string> result;
for (size_t i=0; i<block->size(); i++)
{
auto cmd = *block->get(i);
std::string script;
std::string sep;
for (size_t j=0; j<cmd->size(); j++)
{
script += sep + (*cmd->get(j))->repr();
sep = " ";
}
result.push_back(script);
}
return result;
}
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();
}
std::filesystem::path
Interpreter::format_path(std::filesystem::path path) const
{
if (std::filesystem::exists(path))
{
return std::filesystem::canonical(path);
}
return std::filesystem::absolute(path);
}
}

52
src/Interpreter.hpp Normal file
View File

@ -0,0 +1,52 @@
#ifndef sn_INTERPRETER_HPP
#define sn_INTERPRETER_HPP
#include "commons.hpp"
#include "Node.hpp"
#include "State.hpp"
#include "DAG.hpp"
namespace sn
{
SN_ERROR(run_error);
/**
* Process a snakefile AST and run build commands.
* @see Node
* @see State
**/
class Interpreter
{
public:
explicit Interpreter();
virtual ~Interpreter();
void run();
std::filesystem::path find_snakefile();
std::string load_snakefile();
std::shared_ptr<Node> parse_snakefile();
private:
std::unordered_map<std::filesystem::path,
std::vector<std::filesystem::path>> m_dependencies;
std::unordered_map<std::filesystem::path,
std::vector<std::string>> m_scripts;
std::vector<std::filesystem::path>
targets(std::filesystem::path path) const;
void process(std::shared_ptr<Node> node);
std::vector<std::string> scripts(std::shared_ptr<Node> block) const;
std::string execute(std::string const& script) const;
std::filesystem::path
format_path(std::filesystem::path path) const;
};
}
#endif

154
src/Lexer.cpp Normal file
View File

@ -0,0 +1,154 @@
#include "Lexer.hpp"
namespace sn
{
/*explicit*/ Lexer::Lexer()
{
m_separators.push_back('\n');
m_separators.push_back('\t');
m_separators.push_back('\n');
m_separators.push_back(' ');
add_text("->", NODE_RARROW);
add_text("{", NODE_OBRACE);
add_text("}", NODE_CBRACE);
add_text(",", NODE_COMMA);
m_scanners.push_back(std::bind(&Lexer::scan_ident, this));
}
/*virtual*/ Lexer::~Lexer()
{
}
void Lexer::scan(std::string const& source)
{
m_source = source;
m_cursor = 0;
}
std::optional<std::shared_ptr<Node>> Lexer::next()
{
std::optional<ScanInfo> next_info;
skip_spaces();
for (auto const& scanner: m_scanners)
{
auto info = scanner();
if (// first info
(info && !next_info)
// better info
|| (info && next_info && info->cursor > next_info->cursor))
{
next_info = info;
}
}
if (next_info)
{
auto node = std::make_shared<Node>(next_info->type,
next_info->repr);
m_cursor = next_info->cursor;
return node;
}
return std::nullopt;
}
std::vector<std::shared_ptr<Node>> Lexer::all()
{
std::vector<std::shared_ptr<Node>> result;
while (m_cursor < m_source.size())
{
if (auto n=next();
n)
{
result.push_back(*n);
}
else
{
return result;
}
}
return result;
}
bool Lexer::is_separator(char c) const
{
return std::find(std::begin(m_separators), std::end(m_separators), c)
!= std::end(m_separators);
}
void Lexer::skip_spaces()
{
while (m_cursor < m_source.size()
&& std::isspace(m_source[m_cursor]))
{
m_cursor++;
}
}
std::optional<ScanInfo> Lexer::scan_text(std::string const& text,
NodeType type,
bool has_repr)
{
if (text.size() + m_cursor > m_source.size())
{
return std::nullopt;
}
for (size_t i=0; i<text.size(); i++)
{
if (text[i] != m_source[m_cursor + i])
{
return std::nullopt;
}
}
return ScanInfo {
m_cursor + text.size(),
type,
has_repr ? text : ""
};
}
void Lexer::add_text(std::string const& text,
NodeType type,
bool has_repr/*=false*/)
{
m_scanners.push_back(std::bind(&Lexer::scan_text, this,
text, type, has_repr));
if (text.size() == 1)
{
m_separators.push_back(text[0]);
}
}
std::optional<ScanInfo> Lexer::scan_ident()
{
size_t cursor = m_cursor;
std::string repr;
while (cursor < m_source.size()
&& !is_separator(m_source[cursor]))
{
repr += m_source[cursor];
cursor++;
}
if (repr.empty() == false)
{
return ScanInfo {
cursor,
NODE_IDENT,
repr
};
}
return std::nullopt;
}
}

62
src/Lexer.hpp Normal file
View File

@ -0,0 +1,62 @@
#ifndef sn_LEXER_HPP
#define sn_LEXER_HPP
#include "commons.hpp"
#include "Node.hpp"
namespace sn
{
/**
* Utility to collect and sort scan informations.
* @see Lexer
**/
struct ScanInfo {
size_t cursor;
NodeType type;
std::string repr;
};
/**
* A scanner is a function returning (or not) a scan information.
* @see ScanInfo
**/
using scanner_t = std::function<std::optional<ScanInfo>()>;
/**
* Scan an input source and gives its corresponding tokens.
* @see Node
**/
class Lexer
{
public:
explicit Lexer();
virtual ~Lexer();
void scan(std::string const& source);
std::optional<std::shared_ptr<Node>> next();
std::vector<std::shared_ptr<Node>> all();
private:
std::string m_source;
size_t m_cursor;
std::vector<scanner_t> m_scanners;
std::vector<char> m_separators;
bool is_separator(char c) const;
void skip_spaces();
std::optional<ScanInfo> scan_text(std::string const& text,
NodeType type,
bool has_repr);
void add_text(std::string const& text,
NodeType type,
bool has_repr=false);
std::optional<ScanInfo> scan_ident();
};
}
#endif

56
src/Node.cpp Normal file
View File

@ -0,0 +1,56 @@
#include "Node.hpp"
namespace sn
{
/*explicit*/ Node::Node(NodeType type, std::string const& repr)
: m_type { type }
, m_repr { repr }
{
}
/*virtual*/ Node::~Node()
{
}
void Node::add_child(std::shared_ptr<Node> child)
{
m_children.push_back(child);
}
std::optional<std::shared_ptr<Node>> Node::get(size_t index)
{
if (index < size())
{
return m_children[index];
}
return std::nullopt;
}
std::string Node::string() const
{
std::stringstream ss;
ss << SN_GET(NodeTypeStr, m_type, "NODE_");
if (m_repr.empty() == false)
{
ss << "[" << m_repr << "]";
}
if (size() > 0)
{
ss << "(";
std::string sep;
for (auto const& child: m_children)
{
ss << sep << child->string();
sep = ",";
}
ss << ")";
}
return ss.str();
}
}

41
src/Node.hpp Normal file
View File

@ -0,0 +1,41 @@
#ifndef sn_NODE_HPP
#define sn_NODE_HPP
#include "commons.hpp"
#define NODE_TYPES(G) \
G(NODE_DOC), G(NODE_IDENT), G(NODE_RARROW), G(NODE_OBRACE), \
G(NODE_CBRACE), G(NODE_CMD), G(NODE_CMD_LST), G(NODE_COMMA), \
G(NODE_BLOCK), G(NODE_TARGET), G(NODE_DEPS), G(NODE_RULE)
namespace sn
{
SN_ENUM(NodeType, NODE_TYPES);
/**
* A node is an element of the snake AST.
**/
class Node
{
public:
explicit Node(NodeType type, std::string const& repr="");
virtual ~Node();
NodeType type() const { return m_type; }
std::string repr() const { return m_repr; }
size_t size() const { return m_children.size(); }
void add_child(std::shared_ptr<Node> child);
std::optional<std::shared_ptr<Node>> get(size_t index);
std::string string() const;
private:
NodeType m_type;
std::string m_repr;
std::vector<std::shared_ptr<Node>> m_children;
};
}
#endif

171
src/Parser.cpp Normal file
View File

@ -0,0 +1,171 @@
#include "Parser.hpp"
#include "Node.hpp"
namespace sn
{
/*explicit*/ Parser::Parser()
{
}
/*virtual*/ Parser::~Parser()
{
}
std::shared_ptr<Node> Parser::parse(std::vector<std::shared_ptr<Node>>
const& tokens)
{
m_cursor = 0;
m_tokens = tokens;
return parse_doc();
}
std::optional<std::shared_ptr<Node>>
Parser::consume(NodeType type)
{
auto node = consume();
if ((*node)->type() != type)
{
std::stringstream ss;
ss << "expected '" << SN_GET(NodeTypeStr, type, "NODE_") << "'"
<< " got '" << SN_GET(NodeTypeStr, (*node)->type(), "NODE_") << "'";
throw syntax_error {ss.str()};
}
return node;
}
std::optional<std::shared_ptr<Node>>
Parser::consume()
{
if (m_cursor >= m_tokens.size())
{
return std::nullopt;
}
auto node = m_tokens[m_cursor];
m_cursor++;
return node;
}
bool Parser::type_is(NodeType type, int lookahead/*=0*/) const
{
if (m_cursor + lookahead >= m_tokens.size())
{
return false;
}
return m_tokens[m_cursor + lookahead]->type() == type;
}
std::shared_ptr<Node> Parser::parse_doc()
{
auto node = std::make_shared<Node>(NODE_DOC);
while (m_cursor < m_tokens.size())
{
node->add_child(parse_rule());
}
return node;
}
std::shared_ptr<Node> Parser::parse_rule()
{
auto node = std::make_shared<Node>(NODE_RULE);
auto target = parse_target();
auto deps = parse_deps();
auto block = parse_block();
node->add_child(target);
node->add_child(deps);
node->add_child(block);
return node;
}
std::shared_ptr<Node> Parser::parse_target()
{
auto node = std::make_shared<Node>(NODE_TARGET);
node->add_child(*consume(NODE_IDENT));
while (!type_is(NODE_RARROW))
{
node->add_child(*consume(NODE_IDENT));
}
consume();
return node;
}
std::shared_ptr<Node> Parser::parse_deps()
{
auto node = std::make_shared<Node>(NODE_DEPS);
while (!type_is(NODE_OBRACE))
{
node->add_child(*consume(NODE_IDENT));
}
return node;
}
std::shared_ptr<Node> Parser::parse_block()
{
consume(NODE_OBRACE);
auto node = std::make_shared<Node>(NODE_BLOCK);
auto lst = parse_cmd_lst();
for (size_t i=0; i<lst->size(); i++)
{
node->add_child(*lst->get(i));
}
consume(NODE_CBRACE);
return node;
}
std::shared_ptr<Node> Parser::parse_cmd_lst()
{
auto node = std::make_shared<Node>(NODE_CMD_LST);
if (type_is(NODE_CBRACE))
{
return node;
}
node->add_child(parse_cmd());
while (type_is(NODE_COMMA))
{
consume();
node->add_child(parse_cmd());
}
if (type_is(NODE_COMMA))
{
consume();
}
return node;
}
std::shared_ptr<Node> Parser::parse_cmd()
{
auto node = std::make_shared<Node>(NODE_CMD);
while (!type_is(NODE_CBRACE) && !type_is(NODE_COMMA))
{
node->add_child(*consume(NODE_IDENT));
}
return node;
}
}

43
src/Parser.hpp Normal file
View File

@ -0,0 +1,43 @@
#ifndef sn_PARSER_HPP
#define sn_PARSER_HPP
#include "commons.hpp"
#include "Node.hpp"
namespace sn
{
SN_ERROR(syntax_error);
/**
* Builds the AST given a list of tokens.
* @see Node
* @see Lexer
**/
class Parser
{
public:
explicit Parser();
virtual ~Parser();
std::shared_ptr<Node> parse(std::vector<std::shared_ptr<Node>>
const& tokens);
private:
std::vector<std::shared_ptr<Node>> m_tokens;
size_t m_cursor = 0;
std::optional<std::shared_ptr<Node>> consume(NodeType type);
std::optional<std::shared_ptr<Node>> consume();
bool type_is(NodeType type, int lookahead=0) const;
std::shared_ptr<Node> parse_doc();
std::shared_ptr<Node> parse_rule();
std::shared_ptr<Node> parse_target();
std::shared_ptr<Node> parse_deps();
std::shared_ptr<Node> parse_block();
std::shared_ptr<Node> parse_cmd_lst();
std::shared_ptr<Node> parse_cmd();
};
}
#endif

220
src/State.cpp Normal file
View File

@ -0,0 +1,220 @@
#include "State.hpp"
#include <filesystem>
namespace sn
{
/*explicit*/ State::State()
{
sqlite3_open(m_db_path.c_str(), &m_db);
std::stringstream ss;
ss << "CREATE TABLE IF NOT EXISTS Files ("
<< "path VARCHAR(1024) PRIMARY KEY, "
<< "timestamp VARCHAR(4096))";
size_t const SZ = 1024;
char* status[SZ];
if (sqlite3_exec(m_db,
ss.str().c_str(),
nullptr,
nullptr,
reinterpret_cast<char**>(&status)) != 0)
{
std::cerr << *status << std::endl;
}
}
/*virtual*/ State::~State()
{
sqlite3_close(m_db);
m_db = nullptr;
}
void State::init(std::vector<std::filesystem::path> const& files)
{
for (auto file: files)
{
file = format_path(file);
if (!exists(file))
{
create(file);
}
}
}
void State::update(std::filesystem::path path)
{
path = format_path(path);
long long unsigned timestamp = 0;
if (std::filesystem::exists(path))
{
timestamp = std::filesystem::last_write_time(path)
.time_since_epoch()
.count();
}
std::stringstream ss;
ss << "UPDATE Files SET timestamp = '"
<< timestamp
<< "' WHERE path = '"
<< format_path(path).string()
<< "'";
size_t const SZ = 256;
char* msg[SZ];
int status = sqlite3_exec(m_db, ss.str().c_str(), nullptr, nullptr, msg);
if (status != 0)
{
std::cerr << msg << std::endl;
}
}
std::vector<std::filesystem::path>
State::get_modified_files(std::vector<std::filesystem::path> const& files)
{
std::vector<std::filesystem::path> result;
// Get DB timestamp
// ----------------
auto files_state = load();
for (auto file: files)
{
file = format_path(file);
// Get file timestamp
// ------------------
unsigned long long timestamp = 0;
if (std::filesystem::exists(file))
{
timestamp =
std::filesystem::last_write_time(file)
.time_since_epoch().count();
}
// Compare with DB timestamp
// -------------------------
if (timestamp != files_state[file] || timestamp == 0)
{
result.push_back(file);
}
}
return result;
}
std::filesystem::path
State::format_path(std::filesystem::path path) const
{
if (std::filesystem::exists(path))
{
return std::filesystem::canonical(path);
}
return std::filesystem::absolute(path);
}
bool State::exists(std::filesystem::path file)
{
file = format_path(file);
std::stringstream ss;
ss << "SELECT * FROM Files WHERE path = '"
<< file.string() << "'";
int counter = 0;
sqlite3_exec(m_db, ss.str().c_str(), [](void* data,
int,
char**,
char**){
int* counter = static_cast<int*>(data);
*counter = *counter + 1;
return 0;
}, &counter, nullptr);
return counter > 0;
}
void State::create(std::filesystem::path file)
{
file = format_path(file);
std::stringstream ss;
ss << "INSERT INTO Files (path, timestamp) VALUES ("
<< file << ", '" << 0
<< "');";
size_t const SZ = 1024;
char* msg[SZ];
int status = sqlite3_exec(m_db, ss.str().c_str(),
nullptr, nullptr, msg);
if (status != 0)
{
std::cerr << *msg << std::endl;
}
}
std::unordered_map<std::string, long long unsigned>
State::load()
{
std::stringstream ss;
ss << "SELECT * FROM Files;";
size_t const SZ = 1024;
char* msg [SZ];
std::unordered_map<std::string,
long long unsigned> files_state;
int err = sqlite3_exec(m_db, ss.str().c_str(), [](void* data,
int argc,
char** argv,
char** col) -> int {
std::filesystem::path path;
long long unsigned timestamp = 0;
auto states = (std::unordered_map<std::string, long long unsigned>*) data;
for (int i=0; i<argc; i++)
{
if (strcmp(col[i], "path") == 0)
{
path = argv[i];
}
if (strcmp(col[i], "timestamp") == 0)
{
timestamp = std::stoull(argv[i]);
}
}
(*states)[path] = timestamp;
return 0;
}, &files_state, static_cast<char**>(msg));
if (err != 0)
{
std::cerr << *msg << std::endl;
}
decltype(files_state) results;
for (auto& fs: files_state)
{
results.insert({format_path(fs.first), fs.second});
}
return results;
}
}

45
src/State.hpp Normal file
View File

@ -0,0 +1,45 @@
#ifndef sn_STATE_HPP
#define sn_STATE_HPP
#include <sqlite3.h>
#include <filesystem>
#include "commons.hpp"
namespace sn
{
using file_state_t = std::pair<std::filesystem::path,
long long unsigned>;
/**
* Watches a given set of files.
**/
class State
{
public:
explicit State();
virtual ~State();
void init(std::vector<std::filesystem::path> const& files);
void update(std::filesystem::path path);
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;
private:
sqlite3* m_db = nullptr;
std::filesystem::path m_db_path =
std::filesystem::temp_directory_path() / "snake.db";
bool exists(std::filesystem::path file);
void create(std::filesystem::path file);
std::unordered_map<std::string, long long unsigned> load();
};
}
#endif

34
src/commons.hpp Normal file
View File

@ -0,0 +1,34 @@
#ifndef sn_COMMONS_HPP
#define sn_COMMONS_HPP
#define SN_GEN_ENUM(X) X
#define SN_GEN_STRING(X) #X
#define SN_ENUM(PREFIX, KINDS) \
enum PREFIX { KINDS(SN_GEN_ENUM) }; \
constexpr char const* PREFIX ## Str [] = { KINDS(SN_GEN_STRING) }
#define SN_GET(ARRAY, INDEX, TOREM) \
std::string( ARRAY [INDEX] + strlen(TOREM))
#define SN_ERROR(NAME) \
struct NAME : public std::runtime_error { \
explicit NAME (std::string const& what): std::runtime_error(what) {} \
}
#include <cmath>
#include <cstring>
#include <cassert>
#include <iostream>
#include <sstream>
#include <fstream>
#include <memory>
#include <vector>
#include <unordered_map>
#include <string>
#include <optional>
#include <functional>
#include <algorithm>
#include <filesystem>
#endif

View File

@ -1,8 +1,13 @@
#include <filesystem>
#include <iostream>
#include "snake.hpp"
#include "Interpreter.hpp"
#include "State.hpp"
int main(int, char**)
{
std::cout << "snake v" << SNAKE_VERSION << std::endl;
sn::Interpreter interpreter;
interpreter.run();
return 0;
}

43
tests/Lexer.cpp Normal file
View File

@ -0,0 +1,43 @@
#include <catch2/catch.hpp>
#include "../src/Lexer.hpp"
using namespace sn;
class LexerTest
{
public:
explicit LexerTest() {}
virtual ~LexerTest() {}
void test_next(std::string const& oracle)
{
auto tok = m_lexer.next();
INFO("tok for '" << oracle << "' is nullopt");
REQUIRE(tok);
INFO("expected '" << oracle << "', got '" << (*tok)->string() << "'");
REQUIRE(oracle == (*tok)->string());
}
void test_end()
{
auto tok = m_lexer.next();
INFO("end not found");
REQUIRE(std::nullopt == tok);
}
protected:
Lexer m_lexer;
};
TEST_CASE_METHOD(LexerTest, "Lexer_rule")
{
m_lexer.scan("hello.world, -> { }");
test_next("IDENT[hello.world]");
test_next("COMMA");
test_next("RARROW");
test_next("OBRACE");
test_next("CBRACE");
test_end();
}

48
tests/Parser.cpp Normal file
View File

@ -0,0 +1,48 @@
#include <catch2/catch.hpp>
#include "../src/Lexer.hpp"
#include "../src/Parser.hpp"
using namespace sn;
class ParserTest
{
public:
explicit ParserTest() {}
virtual ~ParserTest() {}
void test_parse(std::string const& oracle,
std::string const& source)
{
Lexer lexer;
lexer.scan(source);
Parser parser;
auto node = parser.parse(lexer.all());
REQUIRE(oracle == node->string());
}
protected:
};
TEST_CASE_METHOD(ParserTest, "Parser_rule")
{
std::stringstream ss;
ss << " hello.elf -> hello.cpp {" << std::endl;
ss << " g++ hello.cpp -o hello.elf, " << std::endl;
ss << " ls " << std::endl;
ss << " }" << std::endl;
test_parse("DOC(RULE(TARGET("
"IDENT[hello.elf]"
"),DEPS("
"IDENT[hello.cpp]"
"),BLOCK("
"CMD("
"IDENT[g++],IDENT[hello.cpp],IDENT[-o],IDENT[hello.elf]"
"),CMD("
"IDENT[ls]"
")"
")))",
ss.str());
}