diff --git a/doc/grammar.bnf b/doc/grammar.bnf index 6b915a6..5558c21 100644 --- a/doc/grammar.bnf +++ b/doc/grammar.bnf @@ -1,7 +1,10 @@ -DOC ::= RULE* +DOC ::= (VAR_DECL | RULE)* RULE ::= TARGET rarrow DEPS BLOCK -TARGET ::= ident+ -DEPS ::= ident* +TARGET ::= LITERAL+ +DEPS ::= LITERAL* BLOCK ::= obrace CMD_LST cbrace CMD_LST ::= (CMD (comma CMD)* comma?)? -CMD ::= ident* +CMD ::= LITERAL* +LITERAL ::= ident | var + +VAR_DECL ::= var assign ident diff --git a/meson.build b/meson.build index 7695a44..bfea405 100644 --- a/meson.build +++ b/meson.build @@ -22,6 +22,7 @@ snake_lib = static_library('snake', 'src/Interpreter.cpp', 'src/State.cpp', 'src/DAG.cpp', + 'src/SymTable.cpp', ], dependencies: [ dependency('sqlite3') diff --git a/src/Interpreter.cpp b/src/Interpreter.cpp index be8e068..1389a5f 100644 --- a/src/Interpreter.cpp +++ b/src/Interpreter.cpp @@ -161,6 +161,23 @@ namespace sn return node; } + std::string Interpreter::get_value(std::shared_ptr node) + { + if (node->type() == NODE_IDENT) + { + return node->repr(); + } + else if (node->type() == NODE_VAR) + { + auto var = *m_sym.get(node->repr()); + return var.value; + } + + throw run_error {"cannot find value for '" + + SN_GET(NodeTypeStr, node->type(), "NODE_") + + "'"}; + } + std::vector Interpreter::targets(std::filesystem::path path) const { @@ -191,6 +208,12 @@ namespace sn } } break; + case NODE_VAR_DECL: { + std::string name = (*node->get(0))->repr(); + auto n = *node->get(1); + m_sym.declare(name, get_value(n)); + } break; + case NODE_RULE: { auto all_targets = *node->get(0); auto deps = *node->get(1); @@ -203,11 +226,11 @@ namespace sn for (size_t j=0; jsize(); j++) { auto dep = *deps->get(j); - auto path = format_path(dep->repr()); + auto path = format_path(get_value(dep)); - m_dependencies[format_path(target->repr())].push_back(path); + m_dependencies[format_path(get_value(target))].push_back(path); - auto target_path = format_path(target->repr()); + auto target_path = format_path(get_value(target)); m_scripts[target_path] = scripts(block); } @@ -237,7 +260,18 @@ namespace sn for (size_t j=0; jsize(); j++) { - script += sep + (*cmd->get(j))->repr(); + auto c = *cmd->get(j); + + if (c->type() == NODE_IDENT) + { + script += sep + c->repr(); + } + else + { + auto var = *m_sym.get(c->repr()); + script += sep + var.value; + } + sep = " "; } diff --git a/src/Interpreter.hpp b/src/Interpreter.hpp index f512893..27b9dd2 100644 --- a/src/Interpreter.hpp +++ b/src/Interpreter.hpp @@ -5,6 +5,7 @@ #include "Node.hpp" #include "State.hpp" #include "DAG.hpp" +#include "SymTable.hpp" namespace sn { @@ -33,6 +34,9 @@ namespace sn std::unordered_map> m_scripts; + SymTable m_sym; + + std::string get_value(std::shared_ptr node); std::vector targets(std::filesystem::path path) const; @@ -45,7 +49,6 @@ namespace sn std::filesystem::path format_path(std::filesystem::path path) const; - }; } diff --git a/src/Lexer.cpp b/src/Lexer.cpp index 883b085..ef0b6d4 100644 --- a/src/Lexer.cpp +++ b/src/Lexer.cpp @@ -9,11 +9,13 @@ namespace sn m_separators.push_back('\n'); m_separators.push_back(' '); + add_text("=", NODE_ASSIGN); add_text("->", NODE_RARROW); add_text("{", NODE_OBRACE); add_text("}", NODE_CBRACE); add_text(",", NODE_COMMA); + m_scanners.push_back(std::bind(&Lexer::scan_var, this)); m_scanners.push_back(std::bind(&Lexer::scan_ident, this)); } @@ -151,4 +153,39 @@ namespace sn return std::nullopt; } + + std::optional Lexer::scan_var() + { + size_t cursor = m_cursor; + std::string repr; + + if (cursor < m_source.size() + && m_source[cursor] == '$') + { + repr += '$'; + cursor++; + } + else + { + return std::nullopt; + } + + while (cursor < m_source.size() + && !is_separator(m_source[cursor])) + { + repr += m_source[cursor]; + cursor++; + } + + if (repr.empty() == false) + { + return ScanInfo { + cursor, + NODE_VAR, + repr.substr(1) + }; + } + + return std::nullopt; + } } diff --git a/src/Lexer.hpp b/src/Lexer.hpp index 5d7789d..0763bcd 100644 --- a/src/Lexer.hpp +++ b/src/Lexer.hpp @@ -56,6 +56,7 @@ namespace sn bool has_repr=false); std::optional scan_ident(); + std::optional scan_var(); }; } diff --git a/src/Node.hpp b/src/Node.hpp index 41978bd..02ed308 100644 --- a/src/Node.hpp +++ b/src/Node.hpp @@ -6,7 +6,8 @@ #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) + G(NODE_BLOCK), G(NODE_TARGET), G(NODE_DEPS), G(NODE_RULE), \ + G(NODE_VAR), G(NODE_ASSIGN), G(NODE_VAR_DECL) namespace sn { diff --git a/src/Parser.cpp b/src/Parser.cpp index c210def..fa5dc6e 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -66,7 +66,16 @@ namespace sn while (m_cursor < m_tokens.size()) { - node->add_child(parse_rule()); + if (type_is(NODE_VAR) + && type_is(NODE_ASSIGN, 1)) + { + node->add_child(parse_var_decl()); + } + else + { + node->add_child(parse_rule()); + } + } return node; @@ -91,11 +100,11 @@ namespace sn { auto node = std::make_shared(NODE_TARGET); - node->add_child(*consume(NODE_IDENT)); + node->add_child(parse_literal()); while (!type_is(NODE_RARROW)) { - node->add_child(*consume(NODE_IDENT)); + node->add_child(parse_literal()); } consume(); @@ -109,7 +118,7 @@ namespace sn while (!type_is(NODE_OBRACE)) { - node->add_child(*consume(NODE_IDENT)); + node->add_child(parse_literal()); } return node; @@ -163,9 +172,34 @@ namespace sn while (!type_is(NODE_CBRACE) && !type_is(NODE_COMMA)) { - node->add_child(*consume(NODE_IDENT)); + node->add_child(parse_literal()); } return node; } + + std::shared_ptr Parser::parse_literal() + { + if (type_is(NODE_IDENT) + || type_is(NODE_VAR)) + { + return *consume(); + } + + throw syntax_error {"unknown token '" + + SN_GET(NodeTypeStr, + m_tokens[m_cursor]->type(), + "NODE_") + + "'"}; + } + + std::shared_ptr Parser::parse_var_decl() + { + auto node = std::make_shared(NODE_VAR_DECL); + node->add_child(*consume(NODE_VAR)); + consume(NODE_ASSIGN); + node->add_child(*consume(NODE_IDENT)); + + return node; + } } diff --git a/src/Parser.hpp b/src/Parser.hpp index ee66de2..2a5a581 100644 --- a/src/Parser.hpp +++ b/src/Parser.hpp @@ -37,6 +37,9 @@ namespace sn std::shared_ptr parse_block(); std::shared_ptr parse_cmd_lst(); std::shared_ptr parse_cmd(); + + std::shared_ptr parse_literal(); + std::shared_ptr parse_var_decl(); }; } diff --git a/src/SymTable.cpp b/src/SymTable.cpp new file mode 100644 index 0000000..062ad58 --- /dev/null +++ b/src/SymTable.cpp @@ -0,0 +1,29 @@ +#include "SymTable.hpp" + +namespace sn +{ + /*explicit*/ SymTable::SymTable() + { + } + + /*virtual*/ SymTable::~SymTable() + { + } + + void SymTable::declare(std::string const& name, std::string const& value) + { + m_vars[name] = Var { value }; + } + + std::optional SymTable::get(std::string const& name) const + { + if (auto var=m_vars.find(name); + var != std::end(m_vars)) + { + return var->second; + } + + return std::nullopt; + } + +} diff --git a/src/SymTable.hpp b/src/SymTable.hpp new file mode 100644 index 0000000..b8f2c8d --- /dev/null +++ b/src/SymTable.hpp @@ -0,0 +1,34 @@ +#ifndef sn_SYMTABLE_HPP +#define sn_SYMTABLE_HPP + +#include "commons.hpp" +#include "Node.hpp" + +namespace sn +{ + /** + * A variable of the snakefile. + **/ + struct Var { + std::string value; + }; + + /** + * A repository to declare and get variables. + * @see Var + **/ + class SymTable + { + public: + explicit SymTable(); + virtual ~SymTable(); + + void declare(std::string const& name, std::string const& value); + std::optional get(std::string const& name) const; + + private: + std::unordered_map m_vars; + }; +} + +#endif diff --git a/tests/Lexer.cpp b/tests/Lexer.cpp index c7c7a25..d79eb73 100644 --- a/tests/Lexer.cpp +++ b/tests/Lexer.cpp @@ -41,3 +41,13 @@ TEST_CASE_METHOD(LexerTest, "Lexer_rule") test_next("CBRACE"); test_end(); } + +TEST_CASE_METHOD(LexerTest, "Lexer_vars") +{ + m_lexer.scan("$hello $WORLD = "); + + test_next("VAR[hello]"); + test_next("VAR[WORLD]"); + test_next("ASSIGN"); + test_end(); +} diff --git a/tests/Parser.cpp b/tests/Parser.cpp index 41641fd..2fb190e 100644 --- a/tests/Parser.cpp +++ b/tests/Parser.cpp @@ -46,3 +46,31 @@ TEST_CASE_METHOD(ParserTest, "Parser_rule") ")))", ss.str()); } + +TEST_CASE_METHOD(ParserTest, "Parser_var_decl_and_use") +{ + std::stringstream ss; + ss << "$BIM = hello.cpp" << std::endl; + ss << "$BAM = world.hpp" << std::endl; + ss << " $BIM -> hello.cpp {" << std::endl; + ss << " g++ $BIM -o hello.elf, " << std::endl; + ss << " ls " << std::endl; + ss << " }" << std::endl; + + test_parse("DOC(" + "VAR_DECL(VAR[BIM],IDENT[hello.cpp])," + "VAR_DECL(VAR[BAM],IDENT[world.hpp])," + "RULE(" + "TARGET(" + "VAR[BIM]" + "),DEPS(" + "IDENT[hello.cpp]" + "),BLOCK(" + "CMD(" + "IDENT[g++],VAR[BIM],IDENT[-o],IDENT[hello.elf]" + "),CMD(" + "IDENT[ls]" + ")" + ")))", + ss.str()); +}