ADD: variable declaration.

main
bog 2023-09-11 08:00:50 +02:00
parent 583c1351ba
commit e5c388a38e
17 changed files with 302 additions and 23 deletions

View File

@ -1,2 +1,6 @@
MODULE ::= EXPR* MODULE ::= EXPR*
EXPR ::= bool EXPR ::=
bool
| ident
| VARDECL
VARDECL ::= opar decl ident EXPR cpar

View File

@ -24,6 +24,7 @@ grino_src = static_library('grino',
'src/Program.cpp', 'src/Program.cpp',
'src/VM.cpp', 'src/VM.cpp',
'src/Value.cpp', 'src/Value.cpp',
'src/SymTable.cpp',
]) ])
grino_dep = declare_dependency(link_with: grino_src) grino_dep = declare_dependency(link_with: grino_src)

View File

@ -1,10 +1,13 @@
#include "Compiler.hpp" #include "Compiler.hpp"
#include "Program.hpp" #include "Program.hpp"
#include "SymTable.hpp"
#include "src/opcodes.hpp"
namespace grino namespace grino
{ {
/*explicit*/ Compiler::Compiler(Logger& logger) /*explicit*/ Compiler::Compiler(Logger& logger, SymTable& sym)
: m_logger { logger } : m_logger { logger }
, m_sym { sym }
{ {
} }
@ -21,6 +24,7 @@ namespace grino
for (size_t i=0; i<node->size(); i++) for (size_t i=0; i<node->size(); i++)
{ {
compile(node->child(i).lock(), program); compile(node->child(i).lock(), program);
program.push_instr(OPCODE_POP);
} }
} break; } break;
@ -32,6 +36,31 @@ namespace grino
program.push_constant(value)); program.push_constant(value));
} break; } break;
case NODE_VARDECL: {
std::string ident = node->child(0).lock()->repr();
auto expr = node->child(1).lock();
size_t address = get_local_address();
compile(expr, program);
m_sym.declare(node->loc(), ident, address);
program.push_instr(OPCODE_STORE_LOCAL, address);
} break;
case NODE_IDENT: {
std::string ident = node->repr();
auto entry = m_sym.find(ident);
if (entry == std::nullopt)
{
std::stringstream ss;
ss << "undefined variable '" << ident << "'";
m_logger.log<compile_error>(LOG_ERROR, node->loc(), ss.str());
}
program.push_instr(OPCODE_LOAD_LOCAL, entry->addr);
} break;
default: default:
std::cerr << "cannot compile node '" std::cerr << "cannot compile node '"
<< GRINO_TRIM(NodeTypeStr[node->type()], "NODE_") << GRINO_TRIM(NodeTypeStr[node->type()], "NODE_")
@ -39,4 +68,11 @@ namespace grino
abort(); abort();
} }
} }
size_t Compiler::get_local_address()
{
static size_t addr = 0;
addr++;
return addr - 1;
}
} }

View File

@ -4,15 +4,19 @@
#include "commons.hpp" #include "commons.hpp"
#include "Logger.hpp" #include "Logger.hpp"
#include "Node.hpp" #include "Node.hpp"
#include "src/mutils.hpp"
namespace grino namespace grino
{ {
class Program; class Program;
class SymTable;
GRINO_ERROR(compile_error);
class Compiler class Compiler
{ {
public: public:
explicit Compiler(Logger& logger); explicit Compiler(Logger& logger, SymTable& sym);
virtual ~Compiler(); virtual ~Compiler();
void compile(std::shared_ptr<Node> node, void compile(std::shared_ptr<Node> node,
@ -20,6 +24,9 @@ namespace grino
private: private:
Logger& m_logger; Logger& m_logger;
SymTable& m_sym;
size_t get_local_address();
}; };
} }

View File

@ -7,8 +7,14 @@ namespace grino
: m_logger { logger } : m_logger { logger }
, m_loc {source_path, 1} , m_loc {source_path, 1}
{ {
add_text(NODE_OPAR, "(", false);
add_text(NODE_CPAR, ")", false);
add_text(NODE_DECL, "$", false);
add_keyword(NODE_BOOL, "true", true); add_keyword(NODE_BOOL, "true", true);
add_keyword(NODE_BOOL, "false", true); add_keyword(NODE_BOOL, "false", true);
m_scanners.push_back(std::bind(&Lexer::scan_ident, this));
} }
/*virtual*/ Lexer::~Lexer() /*virtual*/ Lexer::~Lexer()
@ -125,6 +131,7 @@ namespace grino
text, has_value)); text, has_value));
if (text.size() == 1) if (text.size() == 1)
{ {
m_separators.push_back(text[0]);
} }
} }
@ -141,7 +148,7 @@ namespace grino
std::string const& text, std::string const& text,
bool has_value) bool has_value)
{ {
if (!has_more(m_cursor + text.size())) if (m_cursor + text.size() > m_source.size())
{ {
return std::nullopt; return std::nullopt;
} }
@ -191,4 +198,28 @@ namespace grino
}; };
} }
std::optional<ScanInfo> Lexer::scan_ident()
{
size_t cursor = m_cursor;
std::string repr;
while (has_more(cursor)
&& !is_sep(cursor))
{
repr += at(cursor);
cursor++;
}
if (repr.empty() == false)
{
return ScanInfo {
cursor,
NODE_IDENT,
repr
};
}
return std::nullopt;
}
} }

View File

@ -57,6 +57,7 @@ namespace grino
std::string const& text, std::string const& text,
bool has_value); bool has_value);
std::optional<ScanInfo> scan_ident();
}; };
} }

View File

@ -4,9 +4,14 @@
#include "commons.hpp" #include "commons.hpp"
#include "Loc.hpp" #include "Loc.hpp"
#define NODE_TYPE(G) \ #define NODE_TYPE(G) \
G(NODE_MODULE), \ G(NODE_MODULE), \
G(NODE_BOOL), G(NODE_BOOL), \
G(NODE_VARDECL), \
G(NODE_IDENT), \
G(NODE_OPAR), \
G(NODE_CPAR), \
G(NODE_DECL),
namespace grino namespace grino
{ {

View File

@ -97,9 +97,15 @@ namespace grino
} }
} }
std::shared_ptr<Node> Parser::make_node(NodeType type,
std::string const& repr)
{
return std::make_shared<Node>(type, repr, loc());
}
std::shared_ptr<Node> Parser::parse_module() std::shared_ptr<Node> Parser::parse_module()
{ {
auto node = std::make_shared<Node>(NODE_MODULE, "", loc()); auto node = make_node(NODE_MODULE);
while (m_cursor < m_tokens.size()) while (m_cursor < m_tokens.size())
{ {
@ -111,7 +117,13 @@ namespace grino
std::shared_ptr<Node> Parser::parse_expr() std::shared_ptr<Node> Parser::parse_expr()
{ {
if (type_is(NODE_BOOL)) if (type_is(NODE_OPAR))
{
return parse_vardecl();
}
if (type_is(NODE_IDENT)
|| type_is(NODE_BOOL))
{ {
return consume(); return consume();
} }
@ -126,4 +138,17 @@ namespace grino
return nullptr; return nullptr;
} }
std::shared_ptr<Node> Parser::parse_vardecl()
{
auto node = make_node(NODE_VARDECL);
consume(NODE_OPAR);
consume(NODE_DECL);
node->add_child(consume(NODE_IDENT));
node->add_child(parse_expr());
consume(NODE_CPAR);
return node;
}
} }

View File

@ -29,8 +29,12 @@ namespace grino
bool type_is(std::vector<NodeType> const& types) const; bool type_is(std::vector<NodeType> const& types) const;
Loc loc() const; Loc loc() const;
std::shared_ptr<Node> make_node(NodeType type,
std::string const& repr="");
std::shared_ptr<Node> parse_module(); std::shared_ptr<Node> parse_module();
std::shared_ptr<Node> parse_expr(); std::shared_ptr<Node> parse_expr();
std::shared_ptr<Node> parse_vardecl();
}; };
} }

55
src/SymTable.cpp Normal file
View File

@ -0,0 +1,55 @@
#include "SymTable.hpp"
namespace grino
{
/*explicit*/ SymTable::SymTable(Logger& logger)
: m_logger { logger }
{
}
/*virtual*/ SymTable::~SymTable()
{
}
void SymTable::declare(Loc const& loc, std::string const& name, size_t addr)
{
if (find(name))
{
m_logger.log<symbolic_error>(LOG_ERROR, loc, "'"
+ name
+ "' already defined");
}
SymEntry entry;
entry.addr = addr;
entry.name = name;
entry.is_global = false;
m_entries.push_back(entry);
}
std::optional<SymEntry> SymTable::find(std::string const& name)
{
for (size_t i=0; i<m_entries.size(); i++)
{
if (m_entries[i].name == name)
{
return m_entries[i];
}
}
return std::nullopt;
}
std::string SymTable::string() const
{
std::stringstream ss;
for (auto const& entry: m_entries)
{
ss << entry.addr << "\t" << entry.name << "\n";
}
return ss.str();
}
}

34
src/SymTable.hpp Normal file
View File

@ -0,0 +1,34 @@
#ifndef grino_SYMTABLE_HPP
#define grino_SYMTABLE_HPP
#include "commons.hpp"
#include "Logger.hpp"
namespace grino
{
struct SymEntry {
std::string name;
bool is_global;
size_t addr;
};
GRINO_ERROR(symbolic_error);
class SymTable
{
public:
explicit SymTable(Logger& logger);
virtual ~SymTable();
void declare(Loc const& loc, std::string const& name, size_t addr);
std::optional<SymEntry> find(std::string const& name);
std::string string() const;
private:
Logger& m_logger;
std::vector<SymEntry> m_entries;
};
}
#endif

View File

@ -6,6 +6,7 @@ namespace grino
/*explicit*/ VM::VM(Logger& logger) /*explicit*/ VM::VM(Logger& logger)
: m_logger { logger } : m_logger { logger }
{ {
m_frames.push_back(Frame {});
} }
/*virtual*/ VM::~VM() /*virtual*/ VM::~VM()
@ -29,6 +30,24 @@ namespace grino
m_pc++; m_pc++;
} break; } break;
case OPCODE_POP: {
pop();
m_pc++;
} break;
case OPCODE_STORE_LOCAL: {
size_t addr = *instr.param;
auto value = program.constant(top());
set_local(addr, value);
m_pc++;
} break;
case OPCODE_LOAD_LOCAL: {
size_t addr = *instr.param;
push(program.push_constant(local(addr)));
m_pc++;
} break;
default: default:
std::cerr << "cannot execute unknown opcode " std::cerr << "cannot execute unknown opcode "
<< OpcodeTypeStr[instr.opcode] << OpcodeTypeStr[instr.opcode]
@ -72,4 +91,22 @@ namespace grino
return addr; return addr;
} }
size_t VM::top()
{
size_t addr = m_stack[m_sp - 1];
return addr;
}
std::shared_ptr<Value> VM::local(size_t addr) const
{
return m_frames.back().locals.at(addr);
}
void VM::set_local(size_t addr,
std::shared_ptr<Value> value)
{
m_frames.back().locals[addr] = value;
}
} }

View File

@ -11,6 +11,10 @@ namespace grino
GRINO_ERROR(execution_error); GRINO_ERROR(execution_error);
struct Frame {
std::unordered_map<size_t, std::shared_ptr<Value>> locals;
};
class VM class VM
{ {
public: public:
@ -23,6 +27,7 @@ namespace grino
private: private:
Logger& m_logger; Logger& m_logger;
std::array<size_t, STACK_SIZE> m_stack; std::array<size_t, STACK_SIZE> m_stack;
std::vector<Frame> m_frames;
size_t m_sp; /* stack pointer */ size_t m_sp; /* stack pointer */
size_t m_bp; /* base pointer */ size_t m_bp; /* base pointer */
@ -30,6 +35,10 @@ namespace grino
void push(size_t addr); void push(size_t addr);
size_t pop(); size_t pop();
size_t top();
std::shared_ptr<Value> local(size_t addr) const;
void set_local(size_t addr,
std::shared_ptr<Value> value);
}; };
} }

View File

@ -8,6 +8,7 @@
#include "Program.hpp" #include "Program.hpp"
#include "VM.hpp" #include "VM.hpp"
#include "Logger.hpp" #include "Logger.hpp"
#include "src/SymTable.hpp"
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
@ -73,8 +74,9 @@ int main(int argc, char** argv)
std::cout << "--- ast ---" << std::endl; std::cout << "--- ast ---" << std::endl;
std::cout << ast->string() << std::endl; std::cout << ast->string() << std::endl;
} }
grino::SymTable sym_table {logger};
grino::Compiler compiler {logger}; grino::Compiler compiler {logger, sym_table};
grino::Program program; grino::Program program;
compiler.compile(ast, program); compiler.compile(ast, program);

View File

@ -4,8 +4,11 @@
#include "commons.hpp" #include "commons.hpp"
#include "src/mutils.hpp" #include "src/mutils.hpp"
#define OPCODES(G) \ #define OPCODES(G) \
G(OPCODE_LOAD_CONST) G(OPCODE_LOAD_CONST), \
G(OPCODE_POP), \
G(OPCODE_LOAD_LOCAL), \
G(OPCODE_STORE_LOCAL)
namespace grino namespace grino
{ {

View File

@ -24,13 +24,6 @@ protected:
grino::Logger m_logger; grino::Logger m_logger;
}; };
TEST_CASE_METHOD(LexerTest, "Lexer_errors")
{
grino::Lexer lexer {m_logger, "tests/lexer"};
lexer.scan(" § ");
REQUIRE_THROWS_AS(lexer.next(), grino::lexical_error);
}
TEST_CASE_METHOD(LexerTest, "Lexer_booleans") TEST_CASE_METHOD(LexerTest, "Lexer_booleans")
{ {
grino::Lexer lexer {m_logger, "tests/lexer"}; grino::Lexer lexer {m_logger, "tests/lexer"};
@ -62,3 +55,25 @@ TEST_CASE_METHOD(LexerTest, "Lexer_comments")
test_end(lexer); test_end(lexer);
} }
TEST_CASE_METHOD(LexerTest, "Lexer_vardecl")
{
grino::Lexer lexer {m_logger, "tests/lexer"};
lexer.scan(" $ () coucou) ");
test_next(lexer, "DECL");
test_next(lexer, "OPAR");
test_next(lexer, "CPAR");
test_next(lexer, "IDENT[coucou]");
test_next(lexer, "CPAR");
test_end(lexer);
}
TEST_CASE_METHOD(LexerTest, "Lexer_no_end_space")
{
grino::Lexer lexer {m_logger, "tests/lexer"};
lexer.scan(")");
test_next(lexer, "CPAR");
test_end(lexer);
}

View File

@ -10,14 +10,15 @@ public:
void test_parse(std::string const& oracle, std::string const& source) void test_parse(std::string const& oracle, std::string const& source)
{ {
auto root = m_parser.parse(source); grino::Logger logger;
grino::Lexer lexer {logger, "tests/parser"};
grino::Parser parser {logger, lexer};
auto root = parser.parse(source);
REQUIRE(oracle == root->string()); REQUIRE(oracle == root->string());
} }
protected: protected:
grino::Logger m_logger;
grino::Lexer m_lexer {m_logger, "tests/parser"};
grino::Parser m_parser {m_logger, m_lexer};
}; };
TEST_CASE_METHOD(ParserTest, "Parser_empty") TEST_CASE_METHOD(ParserTest, "Parser_empty")
@ -29,3 +30,12 @@ TEST_CASE_METHOD(ParserTest, "Parser_booleans")
{ {
test_parse("MODULE(BOOL[true],BOOL[false])", "true false"); test_parse("MODULE(BOOL[true],BOOL[false])", "true false");
} }
TEST_CASE_METHOD(ParserTest, "Parser_vardecl")
{
test_parse("MODULE(VARDECL(IDENT[hello],BOOL[false]))",
"($ hello false)");
test_parse("MODULE(VARDECL(IDENT[hello],IDENT[world]))",
"($ hello world)");
}