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:
@ -38,6 +39,6 @@ sudo make install
Don't.
## License
Snake is released under the GPLv3.
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());
}