✨ can now build rules.
parent
01446e331b
commit
e862da9000
|
@ -7,7 +7,8 @@ None for now, but maybe one day...
|
||||||
## How to install
|
## How to install
|
||||||
Snake relies on some libraries you must install in order to compile it.
|
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:
|
First, you need to compile Snake using the given Makefile:
|
||||||
|
|
||||||
|
@ -38,6 +39,6 @@ sudo make install
|
||||||
Don't.
|
Don't.
|
||||||
|
|
||||||
## License
|
## 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.
|
||||||
|
|
|
@ -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*
|
|
@ -16,8 +16,15 @@ configure_file(input: 'src/snake.in.hpp',
|
||||||
|
|
||||||
snake_lib = static_library('snake',
|
snake_lib = static_library('snake',
|
||||||
sources: [
|
sources: [
|
||||||
|
'src/Node.cpp',
|
||||||
|
'src/Lexer.cpp',
|
||||||
|
'src/Parser.cpp',
|
||||||
|
'src/Interpreter.cpp',
|
||||||
|
'src/State.cpp',
|
||||||
|
'src/DAG.cpp',
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
dependency('sqlite3')
|
||||||
])
|
])
|
||||||
|
|
||||||
snake_dep = declare_dependency(link_with: [
|
snake_dep = declare_dependency(link_with: [
|
||||||
|
@ -36,6 +43,8 @@ executable('snake',
|
||||||
executable('snake-tests',
|
executable('snake-tests',
|
||||||
sources: [
|
sources: [
|
||||||
'tests/main.cpp',
|
'tests/main.cpp',
|
||||||
|
'tests/Lexer.cpp',
|
||||||
|
'tests/Parser.cpp',
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
snake_dep,
|
snake_dep,
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
|
@ -1,8 +1,13 @@
|
||||||
|
#include <filesystem>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include "snake.hpp"
|
#include "snake.hpp"
|
||||||
|
#include "Interpreter.hpp"
|
||||||
|
#include "State.hpp"
|
||||||
|
|
||||||
int main(int, char**)
|
int main(int, char**)
|
||||||
{
|
{
|
||||||
std::cout << "snake v" << SNAKE_VERSION << std::endl;
|
sn::Interpreter interpreter;
|
||||||
|
interpreter.run();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
Loading…
Reference in New Issue