Create a minimal interactive shell
This commit is contained in:
28
include/elna/history.hpp
Normal file
28
include/elna/history.hpp
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/core/noncopyable.hpp>
|
||||
#include <string>
|
||||
#include <list>
|
||||
|
||||
namespace elna
|
||||
{
|
||||
struct editor_history : public boost::noncopyable
|
||||
{
|
||||
using const_iterator = std::list<std::string>::const_iterator;
|
||||
|
||||
editor_history();
|
||||
|
||||
void push(const std::string& entry);
|
||||
void clear();
|
||||
|
||||
const_iterator next() noexcept;
|
||||
const_iterator prev() noexcept;
|
||||
const_iterator cbegin() const noexcept;
|
||||
const_iterator cend() const noexcept;
|
||||
const_iterator current() const noexcept;
|
||||
|
||||
private:
|
||||
std::list<std::string> commands;
|
||||
std::list<std::string>::const_iterator current_pointer;
|
||||
};
|
||||
}
|
80
include/elna/interactive.hpp
Normal file
80
include/elna/interactive.hpp
Normal file
@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/core/noncopyable.hpp>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
#include "elna/lexer.hpp"
|
||||
#include "elna/state.hpp"
|
||||
#include "elna/history.hpp"
|
||||
|
||||
namespace elna
|
||||
{
|
||||
/**
|
||||
* Runtime exception for non recoverable errors.
|
||||
*/
|
||||
struct interactive_exception : public std::runtime_error
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
interactive_exception();
|
||||
};
|
||||
|
||||
/**
|
||||
* Main loop.
|
||||
*/
|
||||
void loop();
|
||||
|
||||
/**
|
||||
* Reads the next line.
|
||||
* Returns no value upon receiving end of file.
|
||||
*
|
||||
* \param history Shell history.
|
||||
* \return The read line.
|
||||
*/
|
||||
std::optional<editor_state> read_line(editor_history& history);
|
||||
|
||||
/**
|
||||
* Runs a built-in or a command.
|
||||
*
|
||||
* \param line The command and arguments.
|
||||
* \return Whether the input shoud continued (no exit requested).
|
||||
*/
|
||||
bool execute(const std::vector<token>& line);
|
||||
|
||||
/**
|
||||
* Runs the binary specified in its argument.
|
||||
*
|
||||
* \param program Program name in PATH.
|
||||
* \param arguments Command arguments.
|
||||
*/
|
||||
void launch(const std::string& program, const std::vector<std::string>& arguments);
|
||||
|
||||
/**
|
||||
* Enables the raw mode.
|
||||
*
|
||||
* \return Whether the operation was successful.
|
||||
*/
|
||||
bool enable_raw_mode();
|
||||
|
||||
/**
|
||||
* Disables the raw mode.
|
||||
*/
|
||||
void disable_raw_mode();
|
||||
|
||||
/**
|
||||
* Reads a key.
|
||||
*
|
||||
* \return Read character.
|
||||
*/
|
||||
key read_key();
|
||||
|
||||
/**
|
||||
* Calls autocompletion.
|
||||
*
|
||||
* \return Selected item.
|
||||
*/
|
||||
std::string complete();
|
||||
}
|
85
include/elna/lexer.hpp
Normal file
85
include/elna/lexer.hpp
Normal file
@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "elna/result.hpp"
|
||||
|
||||
namespace elna
|
||||
{
|
||||
/**
|
||||
* Range over the source text that keeps track of the current position.
|
||||
*/
|
||||
struct source
|
||||
{
|
||||
class const_iterator
|
||||
{
|
||||
std::string::const_iterator m_buffer;
|
||||
Position m_position;
|
||||
|
||||
const_iterator(std::string::const_iterator buffer, const Position& position);
|
||||
|
||||
public:
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using difference_type = ptrdiff_t;
|
||||
using value_type = char;
|
||||
using pointer = const value_type *;
|
||||
using reference = const value_type&;
|
||||
|
||||
const Position& position() const noexcept;
|
||||
|
||||
reference operator*() const noexcept;
|
||||
pointer operator->() const noexcept;
|
||||
const_iterator& operator++();
|
||||
const_iterator& operator++(int);
|
||||
bool operator==(const const_iterator& that) const noexcept;
|
||||
bool operator!=(const const_iterator& that) const noexcept;
|
||||
|
||||
friend source;
|
||||
};
|
||||
|
||||
source(const std::string& buffer);
|
||||
const_iterator begin() const;
|
||||
const_iterator end() const;
|
||||
|
||||
private:
|
||||
const std::string& m_buffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Union type representing a single token.
|
||||
*/
|
||||
struct token
|
||||
{
|
||||
/**
|
||||
* Token type.
|
||||
*/
|
||||
enum class type
|
||||
{
|
||||
word,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Type of the token value.
|
||||
*/
|
||||
using value = std::string;
|
||||
|
||||
token(const type of, source::const_iterator begin, source::const_iterator end);
|
||||
|
||||
type of() const noexcept;
|
||||
const value& identifier() const noexcept;
|
||||
const Position& position() const noexcept;
|
||||
|
||||
private:
|
||||
std::string m_value;
|
||||
Position m_position;
|
||||
type m_type;
|
||||
};
|
||||
|
||||
/**
|
||||
* Split the source into tokens.
|
||||
*
|
||||
* \return Tokens or error.
|
||||
*/
|
||||
result<std::vector<token>> lex(const std::string& buffer);
|
||||
}
|
48
include/elna/result.hpp
Normal file
48
include/elna/result.hpp
Normal file
@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <boost/outcome.hpp>
|
||||
|
||||
namespace elna
|
||||
{
|
||||
/**
|
||||
* Position in the source text.
|
||||
*/
|
||||
struct Position
|
||||
{
|
||||
/// Line.
|
||||
std::size_t line = 1;
|
||||
|
||||
/// Column.
|
||||
std::size_t column = 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* A compilation error consists of an error message and position.
|
||||
*/
|
||||
struct CompileError
|
||||
{
|
||||
private:
|
||||
char const *message_;
|
||||
Position position_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @param message Error text.
|
||||
* @param position Error position in the source text.
|
||||
*/
|
||||
CompileError(char const *message, Position position) noexcept;
|
||||
|
||||
/// Error text.
|
||||
char const *message() const noexcept;
|
||||
|
||||
/// Error line in the source text.
|
||||
std::size_t line() const noexcept;
|
||||
|
||||
/// Error column in the source text.
|
||||
std::size_t column() const noexcept;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
using result = boost::outcome_v2::result<T, CompileError>;
|
||||
}
|
160
include/elna/state.hpp
Normal file
160
include/elna/state.hpp
Normal file
@ -0,0 +1,160 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
namespace elna
|
||||
{
|
||||
constexpr const char *erase_line = "\x1b[2K";
|
||||
constexpr const char *start_kitty_keybaord = "\x1b[>1u";
|
||||
constexpr const char *end_kitty_keybaord = "\x1b[<u";
|
||||
|
||||
std::string cursor_to_column(const std::uint16_t position = 1);
|
||||
|
||||
enum class special_key
|
||||
{
|
||||
arrow_left,
|
||||
arrow_right,
|
||||
arrow_up,
|
||||
arrow_down,
|
||||
page_up,
|
||||
page_down,
|
||||
home_key,
|
||||
end_key,
|
||||
};
|
||||
|
||||
/**
|
||||
* Key modifiers.
|
||||
*/
|
||||
enum class modifier : std::uint8_t
|
||||
{
|
||||
shift = 0b1, ///< 1
|
||||
alt = 0b10, ///< 2
|
||||
ctrl = 0b100, ///< 4
|
||||
super = 0b1000, ///< 8
|
||||
hyper = 0b10000, ///< 16
|
||||
meta = 0b100000, ///< 32
|
||||
caps_lock = 0b1000000, ///< 64
|
||||
num_lock = 0b10000000, ///< 128
|
||||
};
|
||||
|
||||
class key
|
||||
{
|
||||
std::variant<special_key, unsigned char> store;
|
||||
std::uint8_t modifiers;
|
||||
|
||||
public:
|
||||
key();
|
||||
explicit key(const unsigned char c, const std::uint8_t modifiers = 0);
|
||||
explicit key(const unsigned char c, const modifier modifiers);
|
||||
explicit key(const special_key control, const std::uint8_t modifiers = 0);
|
||||
explicit key(const special_key control, const modifier modifiers);
|
||||
|
||||
bool operator==(const unsigned char c) const;
|
||||
friend bool operator==(const unsigned char c, const key& that);
|
||||
bool operator==(const special_key control) const;
|
||||
friend bool operator==(const special_key control, const key& that);
|
||||
bool operator==(const key& that) const;
|
||||
bool operator!=(const unsigned char c) const;
|
||||
friend bool operator!=(const unsigned char c, const key& that);
|
||||
bool operator!=(const special_key control) const;
|
||||
friend bool operator!=(const special_key control, const key& that);
|
||||
bool operator!=(const key& that) const;
|
||||
|
||||
unsigned char character() const;
|
||||
special_key control() const;
|
||||
bool modified(const std::uint8_t modifiers) const noexcept;
|
||||
bool modified(const modifier modifiers) const noexcept;
|
||||
|
||||
bool is_character() const noexcept;
|
||||
bool is_control() const noexcept;
|
||||
bool empty() const noexcept;
|
||||
};
|
||||
|
||||
/**
|
||||
* The action should be performed after updating the editor state.
|
||||
*/
|
||||
enum class action
|
||||
{
|
||||
redraw, ///< Redraw everything.
|
||||
write, ///< Write a single character.
|
||||
finalize, ///< Finalize command input.
|
||||
move, ///< Move the cursor.
|
||||
ignore, ///< Do nothing.
|
||||
complete, ///< Complete the input.
|
||||
history, ///< Navigate the history.
|
||||
};
|
||||
|
||||
/**
|
||||
* The line editor with its state.
|
||||
*/
|
||||
class editor_state
|
||||
{
|
||||
std::string input;
|
||||
std::string prompt = "> ";
|
||||
std::size_t position{ 1 };
|
||||
|
||||
public:
|
||||
editor_state();
|
||||
|
||||
/**
|
||||
* Returns the current input gathered by the lin editor.
|
||||
*
|
||||
* \return User input.
|
||||
*/
|
||||
const std::string& command_line() const noexcept;
|
||||
|
||||
/**
|
||||
* Processes the next keypress by changing the line editor state.
|
||||
*
|
||||
* \param key Pressed key.
|
||||
* \return Action to do after the key was pressed.
|
||||
*/
|
||||
action process_keypress(const key& press);
|
||||
|
||||
/**
|
||||
* Clears editors working area and writes drawing sequences into the
|
||||
* buffer.
|
||||
*
|
||||
* \param T Output range for bytes.
|
||||
* \param output Output buffer.
|
||||
*/
|
||||
template<typename T>
|
||||
void draw(T output) const
|
||||
{
|
||||
std::string code;
|
||||
|
||||
std::copy(erase_line, erase_line + strlen(erase_line), output);
|
||||
|
||||
code = cursor_to_column();
|
||||
std::copy(std::cbegin(code), std::cend(code), output);
|
||||
|
||||
std::copy(std::cbegin(this->prompt), std::cend(this->prompt), output);
|
||||
std::copy(std::cbegin(this->input), std::cend(this->input), output);
|
||||
|
||||
code = cursor_to_column(absolute_position());
|
||||
std::copy(std::cbegin(code), std::cend(code), output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the position in the terminal line.
|
||||
*
|
||||
* \return Position in the terminal line.
|
||||
*/
|
||||
std::size_t absolute_position() const noexcept;
|
||||
|
||||
/**
|
||||
* Inserts text at cursor position.
|
||||
*
|
||||
* \param text Text to insert.
|
||||
*/
|
||||
void insert(const std::string& text);
|
||||
|
||||
/**
|
||||
* Replaces the user input with the given string.
|
||||
*
|
||||
* \param text New user input string.
|
||||
*/
|
||||
void load(const std::string& text);
|
||||
};
|
||||
}
|
22
include/elna/tester.hpp
Normal file
22
include/elna/tester.hpp
Normal file
@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace elna
|
||||
{
|
||||
class test_results final
|
||||
{
|
||||
std::uint32_t m_total{ 0 };
|
||||
std::uint32_t m_passed{ 0 };
|
||||
|
||||
public:
|
||||
test_results() = default;
|
||||
|
||||
std::uint32_t total() const noexcept;
|
||||
std::uint32_t passed() const noexcept;
|
||||
std::uint32_t failed() const noexcept;
|
||||
|
||||
int exit_code() const noexcept;
|
||||
void add_exit_code(const int exit_code) noexcept;
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user