ADD: int literal.

main
bog 2023-09-09 15:09:43 +02:00
parent dfd65e5fc3
commit 149f866172
22 changed files with 712 additions and 2 deletions

26
lib/Factory.cpp Normal file
View File

@ -0,0 +1,26 @@
#include "Factory.hpp"
namespace jk
{
/*explicit*/ Factory::Factory(Logger& logger, std::filesystem::path path)
: m_logger { logger }
, m_path { path }
{
}
/*virtual*/ Factory::~Factory()
{
}
std::shared_ptr<Lexer> Factory::make_lexer()
{
Loc loc {m_path, 1, 0};
return std::make_shared<Lexer>(m_logger, loc);
}
std::shared_ptr<Parser> Factory::make_parser()
{
auto lexer = make_lexer();
return std::make_shared<Parser>(m_logger, lexer);
}
}

26
lib/Factory.hpp Normal file
View File

@ -0,0 +1,26 @@
#ifndef jk_FACTORY_HPP
#define jk_FACTORY_HPP
#include "Lexer.hpp"
#include "Parser.hpp"
#include "Logger.hpp"
#include "commons.hpp"
namespace jk
{
class Factory
{
public:
explicit Factory(Logger& logger, std::filesystem::path path);
virtual ~Factory();
std::shared_ptr<Lexer> make_lexer();
std::shared_ptr<Parser> make_parser();
private:
Logger& m_logger;
std::filesystem::path m_path;
};
}
#endif

130
lib/Lexer.cpp Normal file
View File

@ -0,0 +1,130 @@
#include "Lexer.hpp"
namespace jk
{
/*explicit*/ Lexer::Lexer(Logger& logger, Loc const& loc)
: m_logger { logger }
, m_loc { loc }
{
m_scanners.push_back(std::bind(&Lexer::scan_int, this));
}
/*virtual*/ Lexer::~Lexer()
{
}
void Lexer::scan(std::string const& source)
{
m_source = source;
m_cursor = 0;
}
std::shared_ptr<Node> Lexer::next()
{
skip_spaces();
while (more(m_cursor)
&& current(m_cursor) == '#')
{
while (more(m_cursor)
&& current(m_cursor) != '\n')
{
m_cursor++;
}
skip_spaces();
}
std::optional<ScanInfo> info;
for (auto scanner: m_scanners)
{
auto my_info = scanner();
if ((!info && my_info)
|| (info && my_info
&& my_info->cursor > info->cursor))
{
info = my_info;
}
}
if (info)
{
m_cursor = info->cursor;
return std::make_shared<Node>(info->type, info->repr, m_loc);
}
if (more(m_cursor))
{
std::string text;
while (more(m_cursor)
&& !std::isspace(current(m_cursor)))
{
text += current(m_cursor);
m_cursor++;
}
std::stringstream ss;
ss << "unknown text '" << text << "'";
m_logger.log<lexical_error>(LOG_ERROR, m_loc, ss.str());
}
return nullptr;
}
bool Lexer::more(size_t index) const
{
return index < m_source.size();
}
char Lexer::current(size_t index) const
{
assert(more(index));
return m_source[index];
}
void Lexer::skip_spaces()
{
while (more(m_cursor)
&& std::isspace(current(m_cursor)))
{
if (current(m_cursor) == '\n')
{
m_loc = Loc {
m_loc.path(),
m_loc.line() + 1,
m_loc.column()
};
}
m_cursor++;
}
}
std::optional<ScanInfo> Lexer::scan_int() const
{
size_t cursor = m_cursor;
std::string repr;
while (more(cursor)
&& std::isdigit(current(cursor)))
{
repr += current(cursor);
cursor++;
}
if (repr.empty() == false)
{
return ScanInfo {
cursor,
NODE_INT,
repr
};
}
return std::nullopt;
}
}

45
lib/Lexer.hpp Normal file
View File

@ -0,0 +1,45 @@
#ifndef jk_LEXER_HPP
#define jk_LEXER_HPP
#include "commons.hpp"
#include "Logger.hpp"
#include "Node.hpp"
namespace jk
{
JK_ERROR(lexical_error);
struct ScanInfo {
size_t cursor;
NodeType type;
std::string repr;
};
using scanner_t = std::function<std::optional<ScanInfo>()>;
class Lexer
{
public:
explicit Lexer(Logger& logger, Loc const& loc);
virtual ~Lexer();
void scan(std::string const& source);
std::shared_ptr<Node> next();
private:
Logger& m_logger;
Loc m_loc;
size_t m_cursor;
std::string m_source;
std::vector<scanner_t> m_scanners;
bool more(size_t index) const;
char current(size_t index) const;
void skip_spaces();
std::optional<ScanInfo> scan_int() const;
};
}
#endif

15
lib/Loc.cpp Normal file
View File

@ -0,0 +1,15 @@
#include "Loc.hpp"
namespace jk
{
/*explicit*/ Loc::Loc(std::filesystem::path path, int line, int column)
: m_path { path }
, m_line { line }
, m_column { column }
{
}
/*virtual*/ Loc::~Loc()
{
}
}

25
lib/Loc.hpp Normal file
View File

@ -0,0 +1,25 @@
#ifndef jk_LOC_HPP
#define jk_LOC_HPP
#include "commons.hpp"
namespace jk
{
class Loc
{
public:
explicit Loc(std::filesystem::path path, int line, int column);
virtual ~Loc();
std::filesystem::path path() const { return m_path; }
int line() const { return m_line; }
int column() const { return m_column; }
private:
std::filesystem::path m_path;
int m_line;
int m_column;
};
}
#endif

12
lib/Logger.cpp Normal file
View File

@ -0,0 +1,12 @@
#include "Logger.hpp"
namespace jk
{
/*explicit*/ Logger::Logger()
{
}
/*virtual*/ Logger::~Logger()
{
}
}

39
lib/Logger.hpp Normal file
View File

@ -0,0 +1,39 @@
#ifndef jk_LOGGER_HPP
#define jk_LOGGER_HPP
#include "commons.hpp"
#include "Loc.hpp"
#define LOG_TYPE(G) \
G(LOG_ERROR)
namespace jk
{
JK_ENUM(Log, LOG_TYPE);
class Logger
{
public:
explicit Logger();
virtual ~Logger();
template<typename T>
void log(LogType type, Loc const& loc, std::string const& what);
private:
};
template<typename T>
void Logger::log(LogType type, Loc const& loc, std::string const& what)
{
std::stringstream ss;
ss << loc.path().string() << ":" << loc.line();
ss << " " << (std::string(LogTypeStr[type])
.substr(std::string("LOG_").size()));
ss << " " << what;
throw T { ss.str() };
}
}
#endif

57
lib/Node.cpp Normal file
View File

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

39
lib/Node.hpp Normal file
View File

@ -0,0 +1,39 @@
#ifndef jk_NODE_HPP
#define jk_NODE_HPP
#include "commons.hpp"
#include "Loc.hpp"
#define NODE_TYPE(G) \
G(NODE_PROG), \
G(NODE_INT)
namespace jk
{
JK_ENUM(Node, NODE_TYPE);
class Node
{
public:
explicit Node(NodeType type, std::string const& repr, Loc const& loc);
virtual ~Node();
NodeType type() const { return m_type; }
std::string repr() const { return m_repr; }
Loc const& loc() const { return m_loc; }
size_t size() const { return m_children.size(); }
void add_child(std::shared_ptr<Node> child);
std::weak_ptr<Node> child(size_t index) const;
std::string string() const;
private:
NodeType m_type;
std::string m_repr;
Loc m_loc;
std::vector<std::shared_ptr<Node>> m_children;
};
}
#endif

19
lib/Parser.cpp Normal file
View File

@ -0,0 +1,19 @@
#include "Parser.hpp"
namespace jk
{
/*explicit*/ Parser::Parser(Logger& logger, std::shared_ptr<Lexer> lexer)
: m_logger { logger }
, m_lexer { lexer }
{
}
/*virtual*/ Parser::~Parser()
{
}
std::shared_ptr<Node> Parser::parse(std::string const&)
{
return nullptr;
}
}

24
lib/Parser.hpp Normal file
View File

@ -0,0 +1,24 @@
#ifndef jk_PARSER_HPP
#define jk_PARSER_HPP
#include "commons.hpp"
#include "Logger.hpp"
#include "Lexer.hpp"
namespace jk
{
class Parser
{
public:
explicit Parser(Logger& logger, std::shared_ptr<Lexer> lexer);
virtual ~Parser();
std::shared_ptr<Node> parse(std::string const& source);
private:
Logger& m_logger;
std::shared_ptr<Lexer> m_lexer;
};
}
#endif

13
lib/Type.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "Type.hpp"
namespace jk
{
/*explicit*/ Type::Type(TypeType type)
: m_type { type }
{
}
/*virtual*/ Type::~Type()
{
}
}

26
lib/Type.hpp Normal file
View File

@ -0,0 +1,26 @@
#ifndef jk_TYPE_HPP
#define jk_TYPE_HPP
#include "commons.hpp"
#define TYPE_TYPE(G) \
G(TYPE_NIL), \
G(TYPE_INT)
namespace jk
{
JK_ENUM(Type, TYPE_TYPE);
class Type
{
public:
explicit Type(TypeType type);
virtual ~Type();
TypeType type() const { return m_type; }
private:
TypeType m_type;
};
}
#endif

25
lib/Value.cpp Normal file
View File

@ -0,0 +1,25 @@
#include "Value.hpp"
#include "Type.hpp"
namespace jk
{
/*static*/ std::shared_ptr<Value> Value::make_nil()
{
auto value = std::make_shared<Value>();
value->m_type = std::make_shared<Type>(TYPE_NIL);
return value;
}
/*static*/ std::shared_ptr<Value> Value::make_int(int val)
{
auto value = std::make_shared<Value>();
value->m_type = std::make_shared<Type>(TYPE_INT);
value->m_int_val = val;
return value;
}
std::weak_ptr<Type> Value::type() const
{
return m_type;
}
}

29
lib/Value.hpp Normal file
View File

@ -0,0 +1,29 @@
#ifndef jk_VALUE_HPP
#define jk_VALUE_HPP
#include "commons.hpp"
namespace jk
{
class Type;
class Value
{
public:
static std::shared_ptr<Value> make_nil();
static std::shared_ptr<Value> make_int(int val);
explicit Value() = default;
virtual ~Value() = default;
int as_int() const { return *m_int_val; }
std::weak_ptr<Type> type() const;
private:
std::shared_ptr<Type> m_type;
std::optional<int> m_int_val;
};
}
#endif

View File

@ -1,6 +1,18 @@
#ifndef jk_COMMONS_HPP #ifndef jk_COMMONS_HPP
#define jk_COMMONS_HPP #define jk_COMMONS_HPP
#include <cassert>
#include <functional>
#include <optional>
#include <iostream>
#include <vector>
#include <unordered_map>
#include <memory>
#include <string>
#include <sstream>
#include <fstream>
#include <filesystem>
#include "mutils.hpp" #include "mutils.hpp"
#endif #endif

View File

@ -1,4 +1,18 @@
#ifndef jk_MUTILS_HPP #ifndef jk_MUTILS_HPP
#define jk_MUTILS_HPP #define jk_MUTILS_HPP
#include <stdexcept>
#define ENUM_ENUM(X) X
#define ENUM_STRING(X) #X
#define JK_ENUM(PREFIX, DECL) \
enum PREFIX ## Type { DECL(ENUM_ENUM) }; \
constexpr char const* PREFIX ## TypeStr [] { DECL(ENUM_STRING) }
#define JK_ERROR(NAME) \
struct NAME : public std::runtime_error { \
NAME (std::string const& what): std::runtime_error {what} {} \
}
#endif #endif

View File

@ -21,6 +21,18 @@ configure_file(
joko_lib = static_library( joko_lib = static_library(
'joko', 'joko',
sources: [ sources: [
'lib/Node.cpp',
'lib/Loc.cpp',
'lib/Factory.cpp',
'lib/Logger.cpp',
'lib/Lexer.cpp',
'lib/Parser.cpp',
'lib/Type.cpp',
'lib/Value.cpp',
], ],
dependencies: [ dependencies: [
]) ])
@ -38,7 +50,9 @@ executable('joko',
executable('joko-tests', executable('joko-tests',
sources: [ sources: [
'tests/main.cpp' 'tests/main.cpp',
'tests/Lexer.cpp',
'tests/Parser.cpp',
], ],
dependencies: [ dependencies: [
joko_dep, joko_dep,

View File

@ -1,21 +1,26 @@
#include <iostream> #include <iostream>
#include <getopt.h> #include <getopt.h>
#include "config.hpp" #include "config.hpp"
#include "../lib/Lexer.hpp"
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
int index; int index;
bool debug_mode = false;
struct option options[] = { struct option options[] = {
{"debug", no_argument, 0, 'd'},
{"help", no_argument, 0, 'h'}, {"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'v'}, {"version", no_argument, 0, 'v'},
{0, 0, 0, 0} {0, 0, 0, 0}
}; };
int c = getopt_long(argc, argv, "hv", options, &index); int c = getopt_long(argc, argv, "dhv", options, &index);
switch (c) switch (c)
{ {
case 'd': debug_mode = true; break;
case 'h': { case 'h': {
std::cout << "Usage: joko [OPTIONS] source_file" << std::endl; std::cout << "Usage: joko [OPTIONS] source_file" << std::endl;
std::cout << "OPTIONS" << std::endl; std::cout << "OPTIONS" << std::endl;
@ -37,5 +42,51 @@ int main(int argc, char** argv)
} }
if (optind < argc)
{
jk::Logger logger;
jk::Loc loc {argv[optind], 1, 1};
std::ifstream file { argv[optind] };
if (!file)
{
std::stringstream ss;
ss << "cannot find file '" << argv[optind] << "'";
logger.log<std::runtime_error>(jk::LOG_ERROR, loc, ss.str());
}
std::string source;
std::string line;
while (std::getline(file, line))
{
source += line + (file.eof() ? "" : "\n");
}
jk::Lexer lexer {logger, loc};
lexer.scan(source);
if (debug_mode)
{
std::cout << "--- tokens ---" << std::endl;
std::shared_ptr<jk::Node> tok;
std::string sep;
while ( (tok = lexer.next()) )
{
std::cout << sep << tok->string();
sep = " ";
}
std::cout << std::endl;
}
return 0;
}
return 0; return 0;
} }

52
tests/Lexer.cpp Normal file
View File

@ -0,0 +1,52 @@
#include <catch2/catch.hpp>
#include "../lib/Lexer.hpp"
#include "../lib/Factory.hpp"
class LexerTest
{
public:
explicit LexerTest() {}
virtual ~LexerTest() {}
void test_next(jk::Lexer& lexer, std::string const& oracle)
{
auto token = lexer.next();
REQUIRE(token);
REQUIRE(oracle == token->string());
}
void test_end(jk::Lexer& lexer)
{
auto token = lexer.next();
REQUIRE(!token);
}
protected:
jk::Logger m_logger;
};
TEST_CASE_METHOD(LexerTest, "Lexer_int")
{
auto lexer = jk::Factory(m_logger, "tests/lexer").make_lexer();
lexer->scan("4 128 333");
test_next(*lexer, "INT[4]");
test_next(*lexer, "INT[128]");
test_next(*lexer, "INT[333]");
test_end(*lexer);
}
TEST_CASE_METHOD(LexerTest, "Lexer_comments")
{
auto lexer = jk::Factory(m_logger, "tests/lexer").make_lexer();
lexer->scan("4 # 128 \n 333");
test_next(*lexer, "INT[4]");
test_next(*lexer, "INT[333]");
test_end(*lexer);
}
TEST_CASE_METHOD(LexerTest, "Lexer_error")
{
auto lexer = jk::Factory(m_logger, "tests/lexer").make_lexer();
lexer->scan(" § ");
REQUIRE_THROWS_AS(lexer->next(), jk::lexical_error);
}

17
tests/Parser.cpp Normal file
View File

@ -0,0 +1,17 @@
#include <catch2/catch.hpp>
#include "../lib/Parser.hpp"
#include "../lib/Factory.hpp"
class ParserTest
{
public:
explicit ParserTest() {}
virtual ~ParserTest() {}
protected:
jk::Logger m_logger;
};
TEST_CASE_METHOD(ParserTest, "Parser_")
{
}