alec

Abstraction Layer for Escape Codes
git clone git://git.dimitrijedobrota.com/alec.git
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING

commit d705309b300eaf8f7c60a6eb6774f21e5634789c
parent 4f09fd258caac125fa751b1d4864d1276c77076e
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Tue, 20 May 2025 02:36:57 +0200

Get rid of code generation...

Diffstat:
M .clang-format | +++++++++ ---
M .clang-tidy | ++++++++++++++++++++++++++++++++ -------------
M CMakeLists.txt | ++++++ -------------------------------------------------------
M CMakePresets.json | +++ --
M cmake/install-config.cmake | ++ --
M cmake/install-rules.cmake | + -
M example/alec_compile.cpp | ++++ -------
M example/alec_runtime.cpp | ++++++++++ ----------
A include/alec/alec.hpp | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A include/alec/terminal.hpp | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
D source/alec.rules.hpp | ---------------------------------------------------------------------------------
D source/driver.hpp | --------------------------------------
D source/generator.cpp | ---------------------------------------------------------------------------------
D source/generator.h | -------------------
D source/lexer.l | ---------------------------------------------------------------------------------
D source/location.hpp | ------------------
D source/parser.y | ---------------------------------------------------------------------------------

17 files changed, 681 insertions(+), 1149 deletions(-)


diff --git a/ .clang-format b/ .clang-format

@@ -2,12 +2,12 @@

Language: Cpp
# BasedOnStyle: Chromium
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignAfterOpenBracket: BlockIndent
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AlignConsecutiveBitFields: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: DontAlign
AlignEscapedNewlines: Right
AlignOperands: DontAlign
AlignTrailingComments: false
AllowAllArgumentsOnNextLine: true

@@ -17,9 +17,10 @@ AllowShortEnumsOnASingleLine: false

AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortLambdasOnASingleLine: All
AllowShortLambdasOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AllowShortCompoundRequirementOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true

@@ -50,6 +51,7 @@ BreakBeforeBraces: Custom

# BreakBeforeInheritanceComma: true
BreakInheritanceList: BeforeComma
BreakBeforeTernaryOperators: true
BreakBeforeConceptDeclarations: Always
BreakConstructorInitializersBeforeComma: true
BreakConstructorInitializers: BeforeComma
BreakAfterJavaFieldAnnotations: true

@@ -70,6 +72,7 @@ ForEachMacros:

- foreach
- Q_FOREACH
- BOOST_FOREACH
- BASED_FOREACH
IncludeBlocks: Regroup
IncludeCategories:
# Standard library headers come before anything else

@@ -90,6 +93,8 @@ IndentPPDirectives: AfterHash

IndentExternBlock: NoIndent
IndentWidth: 2
IndentWrappedFunctionNames: false
IndentRequiresClause: true
RequiresClausePosition: OwnLine
InsertTrailingCommas: Wrapped
JavaScriptQuotes: Double
JavaScriptWrapImports: true

@@ -98,6 +103,7 @@ MacroBlockBegin: ''

MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
RequiresExpressionIndentation: OuterScope
ObjCBinPackProtocolList: Never
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true

diff --git a/ .clang-tidy b/ .clang-tidy

@@ -3,21 +3,26 @@

# misc-non-private-member-variables-in-classes: the options don't do anything
# modernize-use-nodiscard: too aggressive, attribute is situationally useful
Checks: "*,\
-google-readability-todo,\
-altera-*,\
-cppcoreguidelines-avoid-magic-numbers,\
-boost*,\
-cppcoreguidelines-avoid-do-while,\
-cppcoreguidelines-pro-bounds-constant-array-index,\
-fuchsia-*,\
fuchsia-multiple-inheritance,\
-google-readability-todo,\
-llvm-header-guard,\
-llvm-include-order,\
-llvmlibc-*,\
-modernize-use-nodiscard,\
-misc-include-cleaner,\
-misc-non-private-member-variables-in-classes,\
-misc-no-recursion,\
-modernize-use-trailing-return-type,\
-misc-non-private-member-variables-in-classes,
-readability-magic-numbers
-readability-suspicious-call-argument,\
-*-ranges,\
-*magic*,\
-cppcoreguidelines-missing-std-forward,\
-cppcoreguidelines-rvalue-reference-param-not-moved,\
"
WarningsAsErrors: ''
ExcludeHeaderFilterRegex: 'parser.hpp'
CheckOptions:
- key: 'bugprone-argument-comment.StrictMode'
value: 'true'

@@ -76,9 +81,9 @@ CheckOptions:

- key: 'readability-identifier-naming.ConstexprVariableCase'
value: 'lower_case'
- key: 'readability-identifier-naming.EnumCase'
value: 'CamelCase'
value: 'lower_case'
- key: 'readability-identifier-naming.EnumConstantCase'
value: 'UPPER_CASE'
value: 'lower_case'
- key: 'readability-identifier-naming.FunctionCase'
value: 'lower_case'
- key: 'readability-identifier-naming.GlobalConstantCase'

@@ -132,7 +137,7 @@ CheckOptions:

- key: 'readability-identifier-naming.PublicMethodCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ScopedEnumConstantCase'
value: 'UPPER_CASE'
value: 'lower_case'
- key: 'readability-identifier-naming.StaticConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.StaticVariableCase'

@@ -140,15 +145,17 @@ CheckOptions:

- key: 'readability-identifier-naming.StructCase'
value: 'lower_case'
- key: 'readability-identifier-naming.TemplateParameterCase'
value: 'lower_case'
value: 'CamelCase'
- key: 'readability-identifier-naming.TemplateTemplateParameterCase'
value: 'lower_case'
value: 'CamelCase'
- key: 'readability-identifier-naming.TypeAliasCase'
value: 'lower_case'
- key: 'readability-identifier-naming.TypeAliasIgnoredRegexp'
value: 'N'
- key: 'readability-identifier-naming.TypedefCase'
value: 'lower_case'
- key: 'readability-identifier-naming.TypeTemplateParameterCase'
value: 'lower_case'
value: 'CamelCase'
- key: 'readability-identifier-naming.UnionCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ValueTemplateParameterCase'

@@ -157,4 +164,16 @@ CheckOptions:

value: 'lower_case'
- key: 'readability-identifier-naming.VirtualMethodCase'
value: 'lower_case'
- key: 'readability-identifier-length.IgnoredVariableNames'
value: "^[abcdxyznm]$"
- key: 'readability-identifier-length.IgnoredParameterNames'
value: "^[abcdxyznm]$"
- key: 'google-runtime-int.UnsignedTypePrefix'
value: "u"
- key: 'google-runtime-int.SignedTypePrefix'
value: "i"
- key: 'cppcoreguidelines-missing-std-forward.ForwardFunction'
value: "::based::forward"
- key: 'cppcoreguidelines-rvalue-reference-param-not-moved.MoveFunction'
value: "::based::move"
...

diff --git a/ CMakeLists.txt b/ CMakeLists.txt

@@ -4,7 +4,7 @@ include(cmake/prelude.cmake)


project(
alec
VERSION 0.1.18
VERSION 0.2.0
DESCRIPTION "Abstraction Layer for Escape Codes"
HOMEPAGE_URL "git://git.dimitrijedobrota.com/alec.git"
LANGUAGES CXX

@@ -13,63 +13,14 @@ project(

include(cmake/project-is-top-level.cmake)
include(cmake/variables.cmake)

# ---- Build library ----

set(PARSER_DIR "${CMAKE_CURRENT_BINARY_DIR}/source")
file(MAKE_DIRECTORY ${PARSER_DIR})

find_package(FLEX)
find_package(BISON)

set(LEXER_OUT "${PARSER_DIR}/lexer.cpp")
set(PARSER_OUT "${PARSER_DIR}/parser.cpp")

if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(FLAGS "--debug")
endif()

FLEX_TARGET(LEXER source/lexer.l "${LEXER_OUT}" DEFINES_FILE "${PARSER_DIR}/scanner.hpp" COMPILE_FLAGS "${FLAGS}")
BISON_TARGET(PARSER source/parser.y "${PARSER_OUT}" DEFINES_FILE "${PARSER_DIR}/parser.hpp" COMPILE_FLAGS "${FLAGS}")
ADD_FLEX_BISON_DEPENDENCY(LEXER PARSER)

set_source_files_properties(${LEXER_OUT} PROPERTIES SKIP_LINTING ON)
set_source_files_properties(${PARSER_OUT} PROPERTIES SKIP_LINTING ON)

add_executable(generator "${LEXER_OUT}" "${PARSER_OUT}" source/generator.cpp)
target_include_directories(generator PRIVATE source ${PARSER_DIR})

find_package(cemplate CONFIG REQUIRED)

target_link_libraries(generator PRIVATE cemplate::cemplate)
target_compile_features(generator PUBLIC cxx_std_20)


set(GENERATE_OUT_BIN "${PROJECT_BINARY_DIR}/bin")
set(GENERATE_OUT_INCLUDE "${PROJECT_BINARY_DIR}/include/alec")
file(MAKE_DIRECTORY ${GENERATE_OUT_BIN} ${GENERATE_OUT_INCLUDE})

set_target_properties(generator PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
RUNTIME_OUTPUT_DIRECTORY "${GENERATE_OUT_BIN}"
)

set(RULES_NAME "alec.rules.hpp")
set(RULES_FILE "${GENERATE_OUT_INCLUDE}/${RULES_NAME}")
configure_file(source/alec.rules.hpp ${RULES_FILE} COPYONLY)

add_custom_command(
OUTPUT ${GENERATE_OUT_INCLUDE}/alec.hpp
COMMAND generator ${RULES_FILE} > ${GENERATE_OUT_INCLUDE}/alec.hpp
DEPENDS generator source/${RULES_NAME}
COMMENT "Generating include file"
)

# ---- Declare library ----

add_library(alec_alec INTERFACE ${GENERATE_OUT_INCLUDE}/alec.hpp)
add_library(alec_alec INTERFACE)
add_library(alec::alec ALIAS alec_alec)

find_package(based 0.1.2 CONFIG REQUIRED)
target_link_libraries(alec_alec INTERFACE based::based)

set_property(
TARGET alec_alec PROPERTY
EXPORT_NAME alec

@@ -78,7 +29,7 @@ set_property(

target_include_directories(
alec_alec ${warning_guard}
INTERFACE
"\$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>"
"\$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
)

target_compile_features(alec_alec INTERFACE cxx_std_20)

diff --git a/ CMakePresets.json b/ CMakePresets.json

@@ -75,7 +75,8 @@

"hidden": true,
"cacheVariables": {
"CMAKE_CXX_FLAGS": "/sdl /guard:cf /utf-8 /diagnostics:caret /w14165 /w44242 /w44254 /w44263 /w34265 /w34287 /w44296 /w44365 /w44388 /w44464 /w14545 /w14546 /w14547 /w14549 /w14555 /w34619 /w34640 /w24826 /w14905 /w14906 /w14928 /w45038 /W4 /permissive- /volatile:iso /Zc:inline /Zc:preprocessor /Zc:enumTypes /Zc:lambda /Zc:__cplusplus /Zc:externConstexpr /Zc:throwingNew /EHsc",
"CMAKE_EXE_LINKER_FLAGS": "/machine:x64 /guard:cf"
"CMAKE_EXE_LINKER_FLAGS": "/machine:x64 /guard:cf",
"CMAKE_SHARED_LINKER_FLAGS": "/machine:x64 /guard:cf"
}
},
{

@@ -111,7 +112,7 @@

"cacheVariables": {
"ENABLE_COVERAGE": "ON",
"CMAKE_BUILD_TYPE": "Coverage",
"CMAKE_CXX_FLAGS_COVERAGE": "-Og -g --coverage -fkeep-inline-functions -fkeep-static-functions",
"CMAKE_CXX_FLAGS_COVERAGE": "-O0 -g --coverage -fkeep-inline-functions -fkeep-static-functions",
"CMAKE_EXE_LINKER_FLAGS_COVERAGE": "--coverage",
"CMAKE_SHARED_LINKER_FLAGS_COVERAGE": "--coverage",
"CMAKE_MAP_IMPORTED_CONFIG_COVERAGE": "Coverage;RelWithDebInfo;Release;Debug;"

diff --git a/ cmake/install-config.cmake b/ cmake/install-config.cmake

@@ -1,6 +1,6 @@

include(CMakeFindDependencyMacro)
find_dependency(cemplate)
find_dependency(based)

if(cemplate_FOUND)
if(based_FOUND)
include("${CMAKE_CURRENT_LIST_DIR}/alecTargets.cmake")
endif()

diff --git a/ cmake/install-rules.cmake b/ cmake/install-rules.cmake

@@ -16,7 +16,7 @@ include(GNUInstallDirs)

set(package alec)

install(
DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/
DIRECTORY include/
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
COMPONENT alec_Development
)

diff --git a/ example/alec_compile.cpp b/ example/alec_compile.cpp

@@ -5,9 +5,6 @@


using namespace alec; // NOLINT

using enum Color;
using enum Decor;

int main()
{
std::cout << abuf_enable_v << cursor_hide_v;

@@ -17,11 +14,11 @@ int main()

std::cout << cursor_down_v<3>;
std::cout << foreground_v<30> << background_v<196, 53, 64> << "WORLD!\n";

std::cout << background_v<DEFAULT> << "testing 1...\n"
<< foreground_v<DEFAULT>;
std::cout << background_v<color::def> << "testing 1...\n"
<< foreground_v<color::def>;

std::cout << decor_set_v<INVERSE> << "testing 2...\n"
<< decor_reset_v<INVERSE>;
std::cout << decor_set_v<decor::inverse> << "testing 2...\n"
<< decor_reset_v<decor::inverse>;

std::cout << cursor_up_v<5> << "Hello there!" << cursor_save_v;
std::cout << cursor_down_v<10> << "General Kenobi!";

diff --git a/ example/alec_runtime.cpp b/ example/alec_runtime.cpp

@@ -5,29 +5,29 @@


using namespace alec; // NOLINT

using enum Color;
using enum Decor;

int main()
{
std::cout << abuf_enable() << cursor_hide();
std::cout << abuf_enable_v << cursor_hide_v;

std::cout << cursor_position(1, 1) << foreground(91) << "HELLO!\n";

std::cout << cursor_down(3);
std::cout << foreground(30) << background(96, 53, 64) << "WORLD!\n";

std::cout << background(DEFAULT) << "testing 1...\n" << foreground(DEFAULT);
std::cout << decor_set(INVERSE) << "testing 2...\n" << decor_reset(INVERSE);
std::cout << background(color::def) << "testing 1...\n"
<< foreground(color::def);

std::cout << decor_set(decor::inverse) << "testing 2...\n"
<< decor_reset(decor::inverse);

std::cout << cursor_up(5) << "Hello there!" << cursor_save();
std::cout << cursor_up(5) << "Hello there!" << cursor_save_v;
std::cout << cursor_down(10) << "General Kenobi!";
std::cout << cursor_position(10, 40) << "no pain no gain" << cursor_restore()
<< cursor_show();
std::cout << cursor_position(10, 40) << "no pain no gain" << cursor_restore_v
<< cursor_show_v;

(void)std::getchar();

std::cout << abuf_disable();
std::cout << abuf_disable_v;

return 0;
}

diff --git a/ include/alec/alec.hpp b/ include/alec/alec.hpp

@@ -0,0 +1,363 @@

#pragma once

#include <algorithm>
#include <array>
#include <cassert>
#include <string>

#include <based/enum/enum.hpp>
#include <based/string/literal.hpp>
#include <based/types/types.hpp>

namespace alec
{

#define ENUM_COLOR \
black, red, green, yellow, blue, magenta, cyan, white, unused_1, def
BASED_DECLARE_ENUM(color, based::bi8, 0, ENUM_COLOR)
BASED_DEFINE_ENUM(color, based::bi8, 0, ENUM_COLOR)
#undef ENUM_COLOR

#define ENUM_DECOR \
reset, bold, dim, italic, underline, blink, unused_1, inverse, hide, strike
BASED_DECLARE_ENUM(decor, based::bi8, 0, ENUM_DECOR)
BASED_DEFINE_ENUM(decor, based::bi8, 0, ENUM_DECOR)
#undef ENUM_DECOR

#define ENUM_MOTION end, begin, whole
BASED_DECLARE_ENUM(motion, based::bi8, 0, ENUM_MOTION)
BASED_DEFINE_ENUM(motion, based::bi8, 0, ENUM_MOTION)
#undef ENUM_MOTION

namespace detail
{

namespace helper
{
template<std::size_t n>
static constexpr std::size_t size(based::string_literal<n> /*val*/)
{
return n;
}
static constexpr std::size_t size(char /*val*/)
{
return 1;
}

template<class T>
static constexpr std::size_t size(T val)
{
std::size_t len = 1;
while ((val /= 10) != 0) {
len++;
}
return len;
}

template<std::size_t n>
static constexpr char* append(char* ptr, based::string_literal<n> val)
{
std::copy_n(val.data(), n, ptr);
return ptr + n; // NOLINT
}

static constexpr char* append(char* ptr, char val)
{
*ptr++ = val; // NOLINT
return ptr;
}

template<class T>
static constexpr char* append(char* ptr, T val)
{
char* tmp = ptr += size(val); // NOLINT
do { // NOLINT
*--tmp = '0' + static_cast<char>(val % 10); // NOLINT
} while ((val /= 10) != 0);
return ptr;
}

static constexpr std::string make(auto... args)
{
std::string res((helper::size(args) + ... + 2), 0);
res[0] = 0x1B, res[1] = '[';
auto* ptr = res.data() + 2; // NOLINT
((ptr = helper::append(ptr, args)), ...);
return res;
}

template<auto... args>
struct escape_t
{
static constexpr const auto value = []()
{
std::array<char, (helper::size(args) + ... + 3)> arr = {};
arr[0] = 0x1B, arr[1] = '[';
auto* ptr = arr.data() + 2;
((ptr = helper::append(ptr, args)), ...);
return arr;
}();
static constexpr auto data = value.data();
};

} // namespace helper

template<auto... args>
static constexpr auto escape = alec::detail::helper::escape_t<args...>::data;

template<based::string_literal... strs>
static constexpr auto escape_literal = escape<strs...>;

} // namespace detail

// Template compile-time variables

// Forward-declare templates

template<auto... val>
static const char* const background_v = "";

template<auto... val>
static const char* const foreground_v = "";

// Template specializations

// Move cursor up/down/frwd/back
template<unsigned cnt>
static constexpr auto cursor_up_v = detail::escape<cnt, 'A'>;

template<unsigned cnt>
static constexpr auto cursor_down_v = detail::escape<cnt, 'B'>;

template<unsigned cnt>
static constexpr auto cursor_frwd_v = detail::escape<cnt, 'C'>;

template<unsigned cnt>
static constexpr auto cursor_back_v = detail::escape<cnt, 'D'>;

// Move cursor to the next/prev line
template<unsigned cnt>
static constexpr auto cursor_line_next_v = detail::escape<cnt, 'E'>;

template<unsigned cnt>
static constexpr auto cursor_line_prev_v = detail::escape<cnt, 'F'>;

// Set cursor to specific column
template<unsigned col>
static constexpr auto cursor_column_v = detail::escape<col, 'G'>;

// Erase functions
template<motion::type motion>
static constexpr auto erase_display_v = detail::escape<motion.value, 'J'>;

template<motion::type motion>
static constexpr auto erase_line_v = detail::escape<motion.value, 'K'>;

// Scroll up/down
template<unsigned cnt>
static constexpr auto scroll_up_v = detail::escape<cnt, 'S'>;

template<unsigned cnt>
static constexpr auto scroll_down_v = detail::escape<cnt, 'T'>;

// Set cursor to a specific position
template<unsigned row, unsigned col>
static constexpr auto cursor_position_v = detail::escape<row, ';', col, 'H'>;

// color
// palet colors
template<color::type color>
static constexpr auto foreground_v<color> =
detail::escape<color.value + 30, 'm'>;

template<color::type color>
static constexpr auto background_v<color> =
detail::escape<color.value + 40, 'm'>;

// 256-color palette
template<based::bu8 idx>
static constexpr auto foreground_v<idx> =
detail::escape<38, ';', 5, ';', idx, 'm'>;

template<based::bu8 idx>
static constexpr auto background_v<idx> =
detail::escape<48, ';', 5, ';', idx, 'm'>;

// RGB colors
template<based::bu8 red, based::bu8 green, based::bu8 blue>
static constexpr auto foreground_v<red, green, blue> =
detail::escape<38, ';', 2, ';', red, ';', green, ';', blue, 'm'>;

template<based::bu8 red, based::bu8 green, based::bu8 blue>
static constexpr auto background_v<red, green, blue> =
detail::escape<48, ';', 2, ';', red, ';', green, ';', blue, 'm'>;

// Set/reset text decorators
template<decor::type decor>
static constexpr auto decor_set_v = detail::escape<decor.value, 'm'>;

template<decor::type decor>
static constexpr auto decor_reset_v = detail::escape<decor.value + 20, 'm'>;

// Save/restore cursor position;
static constexpr auto cursor_save_v = detail::escape<'s'>;

static constexpr auto cursor_restore_v = detail::escape<'u'>;

// Set screen modes
template<unsigned mode>
static constexpr auto screen_mode_set_v = detail::escape<'=', mode, 'h'>;

template<unsigned mode>
static constexpr auto screen_mode_reset_v = detail::escape<'=', mode, 'l'>;

// Private screen modes supported by most terminals
// Save/restore screen
static constexpr auto screen_save_v = detail::escape_literal<"?47h">;

static constexpr auto screen_restore_v = detail::escape_literal<"?47l">;

// Show/hide cursor
static constexpr auto cursor_show_v = detail::escape_literal<"?25h">;

static constexpr auto cursor_hide_v = detail::escape_literal<"?25l">;

// Enable/disable alternate buffer
static constexpr auto abuf_enable_v = detail::escape_literal<"?1049h">;

static constexpr auto abuf_disable_v = detail::escape_literal<"?1049l">;

// Enable/disable bracketed paste mode
static constexpr auto paste_enable_v = detail::escape_literal<"?2004h">;

static constexpr auto paste_disable_v = detail::escape_literal<"?2004l">;

// Run-time functions

// Move cursor up/down/frwd/back
static constexpr auto cursor_up(unsigned cnt)
{
return detail::helper::make(cnt, 'A');
}

static constexpr auto cursor_down(unsigned cnt)
{
return detail::helper::make(cnt, 'B');
}

static constexpr auto cursor_frwd(unsigned cnt)
{
return detail::helper::make(cnt, 'C');
}

static constexpr auto cursor_back(unsigned cnt)
{
return detail::helper::make(cnt, 'D');
}

// Move cursor to the next/prev line
static constexpr auto cursor_line_next(unsigned cnt)
{
return detail::helper::make(cnt, 'E');
}

static constexpr auto cursor_line_prev(unsigned cnt)
{
return detail::helper::make(cnt, 'F');
}

// Set cursor to specific column
static constexpr auto cursor_column(unsigned col)
{
return detail::helper::make(col, 'G');
}

// Erase functions
static constexpr auto erase_display(motion::type motion)
{
return detail::helper::make(motion.value, 'J');
}

static constexpr auto erase_line(motion::type motion)
{
return detail::helper::make(motion.value, 'K');
}

// Scroll up/down
static constexpr auto scroll_up(unsigned cnt)
{
return detail::helper::make(cnt, 'S');
}

static constexpr auto scroll_down(unsigned cnt)
{
return detail::helper::make(cnt, 'T');
}

// Set cursor to a specific position
static constexpr auto cursor_position(unsigned row, unsigned col)
{
return detail::helper::make(row, ';', col, 'H');
}

// color
// palet colors
static constexpr auto foreground(color::type color)
{
return detail::helper::make(color.value + 30, 'm');
}

static constexpr auto background(color::type color)
{
return detail::helper::make(color.value + 40, 'm');
}

// 256-color palette
static constexpr auto foreground(based::bu8 idx)
{
return detail::helper::make(38, ';', 5, ';', idx, 'm');
}

static constexpr auto background(based::bu8 idx)
{
return detail::helper::make(48, ';', 5, ';', idx, 'm');
}

// RGB colors
static constexpr auto foreground(
based::bu8 red, based::bu8 green, based::bu8 blue
)
{
return detail::helper::make(38, ';', 2, ';', red, ';', green, ';', blue, 'm');
}

static constexpr auto background(
based::bu8 red, based::bu8 green, based::bu8 blue
)
{
return detail::helper::make(48, ';', 2, ';', red, ';', green, ';', blue, 'm');
}

// Set/reset text decorators
static constexpr auto decor_set(decor::type decor)
{
return detail::helper::make(decor.value, 'm');
}

static constexpr auto decor_reset(decor::type decor)
{
return detail::helper::make(decor.value + 20, 'm');
}

// Set screen modes
static constexpr auto screen_mode_set(unsigned mode)
{
return detail::helper::make('=', mode, 'h');
}

static constexpr auto screen_mode_reset(unsigned mode)
{
return detail::helper::make('=', mode, 'l');
}

} // namespace alec

diff --git a/ include/alec/terminal.hpp b/ include/alec/terminal.hpp

@@ -0,0 +1,251 @@

#pragma once

#include <cstdint>
#include <optional>
#include <stdexcept>

#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>

namespace alec
{

class runtime_error : public std::runtime_error
{
public:
explicit runtime_error(const std::string& err)
: std::runtime_error(err)
{
}
};

enum error_code_t // NOLINT
{
FDNTERM,
TERMIOSRD,
TERMIOSWR,
BUFFULL,
CHARRD,
IOCTL,
SCREENSZ
};

template<error_code_t e>
class error : public runtime_error
{
public:
explicit error()
: runtime_error(error_get_message(e))
{
}

private:
static std::string error_get_message(error_code_t error)
{
switch (error) {
case error_code_t::FDNTERM:
return "File descriptor is not associated with a terminal";
case error_code_t::TERMIOSRD:
return "Can't read termios";
case error_code_t::TERMIOSWR:
return "Can't write termios";
case error_code_t::BUFFULL:
return "Buffer is full";
case error_code_t::CHARRD:
return "Can't read character";
case error_code_t::IOCTL:
return "ioctl error";
case error_code_t::SCREENSZ:
return "Can't determine the screen size";
}

return "alec error, should not happen...";
}
};

class buffer
{
public:
explicit buffer(int fdsc)
: m_fd(fdsc)
, m_orig_termios()
{
if (isatty(m_fd) == 0) {
throw error<error_code_t::FDNTERM>();
}

if (tcgetattr(m_fd, &m_orig_termios) == -1) {
throw error<error_code_t::TERMIOSRD>();
}

struct termios raw = m_orig_termios;

// NOLINTBEGIN
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
raw.c_oflag &= ~(OPOST);
raw.c_cflag |= (CS8);
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN); // | ISIG
raw.c_cc[VMIN] = 0;
raw.c_cc[VTIME] = 1;
// NOLINTEND

/* put terminal in raw mode after flushing */
if (tcsetattr(m_fd, TCSAFLUSH, &raw) < 0) {
throw error<error_code_t::TERMIOSWR>();
}
}

buffer(const buffer&) = delete;
buffer& operator=(const buffer&) = delete;

buffer(buffer&&) = default;
buffer& operator=(buffer&&) = default;

~buffer() { tcsetattr(m_fd, TCSAFLUSH, &m_orig_termios); }

uint8_t read()
{
if (m_start == m_end && get() == 0) {
return 0;
}

uint8_t chr = m_buffer[m_start]; // NOLINT
m_start = (m_start + 1) % m_buffer.size();
return chr;
}

uint8_t read_blocking()
{
while (m_start == m_end) {
get();
}

uint8_t chr = m_buffer[m_start]; // NOLINT
m_start = (m_start + 1) % m_buffer.size();
return chr;
}

void flush()
{
while (get() != 0) {
}
}

private:
size_t get()
{
ssize_t scnt = -1;
if ((m_end + 1) % m_buffer.size() == m_start) {
throw error<error_code_t::BUFFULL>();
}

if (m_start <= m_end) {
scnt = ::read(m_fd, m_buffer.data() + m_end, m_buffer.size() - m_end);
} else {
scnt = ::read(m_fd, m_buffer.data() + m_end, m_start - m_end);
}

if (scnt == -1) {
throw error<error_code_t::CHARRD>();
}

const auto cnt = static_cast<size_t>(scnt);
m_end = (m_end + cnt) % m_buffer.size();
return cnt;
}

std::array<uint8_t, 1024> m_buffer = {0};
int m_fd = 0;

uint64_t m_start = 0;
uint64_t m_end = 0;

struct termios m_orig_termios;
};

inline auto& get_buffer()
{
static std::optional<buffer> ibuf;
return ibuf;
}

inline void init_buffer(int fdsc)
{
get_buffer().emplace(fdsc);
}

inline void dest_buffer()
{
get_buffer().reset();
}

inline std::pair<std::uint16_t, std::uint16_t> get_screen_size()
{
#ifdef TIOCGSIZE
struct ttysize tts = {};
if (ioctl(STDIN_FILENO, TIOCGSIZE, &tts) == -1) { // NOLINT
throw error<error_code_t::IOCTL>();
}
return {tts.ts_cols, tts.ts_lines};
#elif defined(TIOCGWINSZ)
struct winsize tts = {};
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &tts) == -1) { // NOLINT
throw error<error_code_t::IOCTL>();
}
return {tts.ws_col, tts.ws_row};
#endif /* TIOCGSIZE */

throw error<error_code_t::SCREENSZ>();
}

class event
{
public:
enum class Type : std::uint8_t
{
NONE = 0,
KEY = 1,
RESIZE = 2,
MOUSE = 3,
};

enum class Mod : std::uint8_t
{
ALT = 1,
CTRL = 2,
SHIFT = 4,
MOTION = 8,
};

event(Type type, uint8_t mod_mask, uint8_t key) // NOLINT
: m_type(type)
, m_mod_mask(mod_mask)
, m_key(key)
{
}

const auto& type() const { return m_type; }
auto& type() { return m_type; }

const auto& key() const { return m_key; }
auto& key() { return m_key; }

const auto& mod_mask() const { return m_mod_mask; }
auto& mod_mask() { return m_mod_mask; }

bool is_set(uint8_t mask) const { return mask == (m_mod_mask & mask); }

private:
Type m_type = Type::NONE;
uint8_t m_mod_mask = 0;
uint8_t m_key = 0;
};

inline event get_event()
{
const auto chr = get_buffer().value().read();
return {chr != 0 ? event::Type::KEY : event::Type::NONE, 0, chr};
}

} // namespace alec

diff --git a/ source/alec.rules.hpp b/ source/alec.rules.hpp

@@ -1,611 +0,0 @@

#pragma once

#include <algorithm>
#include <array>
#include <cassert>
#include <cstdint>
#include <optional>
#include <stdexcept>
#include <string>

#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>

namespace alec
{

enum Ctrl : std::uint8_t
{
BELL = 0x07,
BS = 0x08,
HT = 0x09,
LF = 0x0A,
VT = 0x0B,
FF = 0x0C,
CR = 0x0D,
ESC = 0x1B,
DEL = 0x7F,
};

enum class Color : std::uint8_t
{
BLACK = 0,
RED = 1,
GREEN = 2,
YELLOW = 3,
BLUE = 4,
MAGENTA = 5,
CYAN = 6,
WHITE = 7,
DEFAULT = 9,
};

enum class Decor : std::uint8_t
{
RESET = 0,
BOLD = 1,
DIM = 2,
ITALIC = 3,
UNDERLINE = 4,
BLINK = 5,
INVERSE = 7,
HIDE = 8,
STRIKE = 9,
};

enum class Motion : std::uint8_t
{
END = 0,
BEGIN = 1,
WHOLE = 2,
};

namespace details
{

template<std::size_t n>
struct string_literal
{
constexpr string_literal(const char (&str)[n]) // NOLINT
: m_value(std::to_array(str))
{
} // NOLINT

constexpr std::size_t size() const { return n; }
constexpr const char* data() const { return m_value.data(); }

std::array<char, n> m_value;
};

namespace helper
{
template<std::size_t n>
static constexpr std::size_t size(string_literal<n> /*val*/)
{
return n;
}
static constexpr std::size_t size(char /*val*/)
{
return 1;
}
static constexpr std::size_t size(int val)
{
std::size_t len = 1;
while ((val /= 10) != 0) {
len++;
}
return len;
}

template<std::size_t n>
static constexpr char* append(char* ptr, string_literal<n> val)
{
std::copy_n(val.data(), n, ptr);
return ptr + n; // NOLINT
}

static constexpr char* append(char* ptr, char val)
{
*ptr++ = val; // NOLINT
return ptr;
}

static constexpr char* append(char* ptr, int val)
{
char* tmp = ptr += size(val); // NOLINT
do { // NOLINT
*--tmp = '0' + static_cast<char>(val % 10); // NOLINT
} while ((val /= 10) != 0);
return ptr;
}

static constexpr std::string make(auto... args)
{
std::string res((helper::size(args) + ... + 2), 0);
res[0] = Ctrl::ESC, res[1] = '[';
auto* ptr = res.data() + 2; // NOLINT
((ptr = helper::append(ptr, args)), ...);
return res;
}

template<auto... args>
struct escape_t
{
static constexpr const auto value = []()
{
std::array<char, (helper::size(args) + ... + 3)> arr = {Ctrl::ESC, '[', 0};
auto* ptr = arr.data() + 2;
((ptr = helper::append(ptr, args)), ...);
return arr;
}();
static constexpr auto data = value.data();
};

} // namespace helper

template<auto... args>
static constexpr auto escape = alec::details::helper::escape_t<args...>::data;

template<details::string_literal... strs>
static constexpr auto escape_literal = escape<strs...>;

} // namespace details

// Tamplate parameter constraints

template<int n>
concept limit_256_v = n >= 0 && n < 256;

template<int n>
concept limit_pos_v = n >= 0;

static constexpr bool limit_pos(int n)
{
return n >= 0;
}
static constexpr bool limit_256(int n)
{
return n >= 0 && n < 256;
}

/*%%*//*
// NOLINTBEGIN (*cast*)

// Move cursor up/down/frwd/back

cursor_up
int cnt
limit_pos
cnt, 'A'

cursor_down
int cnt
limit_pos
cnt, 'B'

cursor_frwd
int cnt
limit_pos
cnt, 'C'

cursor_back
int cnt
limit_pos
cnt, 'D'

// Move cursor to the next/prev line

cursor_line_next
int cnt
limit_pos
cnt, 'E'

cursor_line_prev
int cnt
limit_pos
cnt, 'F'

// Set cursor to specific column

cursor_column
int col
limit_pos
col, 'G'

// Erase functions

erase_display
Motion mtn
|
int(mtn), 'J'

erase_line
Motion mtn
|
int(mtn), 'K'

// Scroll up/down

scroll_up
int cnt
limit_pos
cnt, 'S'

scroll_down
int cnt
limit_pos
cnt, 'T'

// Set cursor to a specific position

cursor_position
int row, int col
limit_pos
row, ';', col, 'H'

// color

// palet colors

foreground
Color color
|
int(color) + 30, 'm'

background
Color color
|
int(color) + 40, 'm'

// 256-color palette

foreground
int idx
limit_256
38, ';', 5, ';', idx, 'm'

background
int idx
limit_256
48, ';', 5, ';', idx, 'm'

// RGB colors

foreground
int red, int green, int blue
limit_256
38, ';', 2, ';', red, ';', green, ';', blue, 'm'

background
int red, int green, int blue
limit_256
48, ';', 2, ';', red, ';', green, ';', blue, 'm'

// Set/reset text decorators

decor_set
Decor decor
|
int(decor), 'm'

decor_reset
Decor decor
|
int(decor) + 20, 'm'

// Save/restore cursor position;

cursor_save
|
|
's'

cursor_restore
|
|
'u'

// Set screen modes

screen_mode_set
int mode
limit_pos
'=', mode, 'h'

screen_mode_reset
int mode
limit_pos
'=', mode, 'l'

// Private screen modes supported by most terminals

// Save/restore screen

screen_save
|
|
"?47h"

screen_restore
|
|
"?47l"

// Show/hide cursor

cursor_show
|
|
"?25h"

cursor_hide
|
|
"?25l"

// Enable/disable alternate buffer

abuf_enable
|
|
"?1049h"

abuf_disable
|
|
"?1049l"

// Enable/disable bracketed paste mode

paste_enable
|
|
"?2004h"

paste_disable
|
|
"?2004l"

// NOLINTEND (*cast*)
*//*%%*/

class runtime_error : public std::runtime_error
{
public:
explicit runtime_error(const std::string& err)
: std::runtime_error(err)
{
}
};

enum error_code_t // NOLINT
{
FDNTERM,
TERMIOSRD,
TERMIOSWR,
BUFFULL,
CHARRD,
IOCTL,
SCREENSZ
};

template<error_code_t e>
class error : public runtime_error
{
public:
explicit error()
: runtime_error(error_get_message(e))
{
}

private:
static std::string error_get_message(error_code_t error)
{
switch (error) {
case error_code_t::FDNTERM:
return "File descriptor is not associated with a terminal";
case error_code_t::TERMIOSRD:
return "Can't read termios";
case error_code_t::TERMIOSWR:
return "Can't write termios";
case error_code_t::BUFFULL:
return "Buffer is full";
case error_code_t::CHARRD:
return "Can't read character";
case error_code_t::IOCTL:
return "ioctl error";
case error_code_t::SCREENSZ:
return "Can't determine the screen size";
}

return "alec error, should not happen...";
}
};

class buffer
{
public:
explicit buffer(int fdsc)
: m_fd(fdsc)
, m_orig_termios()
{
if (isatty(m_fd) == 0) {
throw error<error_code_t::FDNTERM>();
}

if (tcgetattr(m_fd, &m_orig_termios) == -1) {
throw error<error_code_t::TERMIOSRD>();
}

struct termios raw = m_orig_termios;

// NOLINTBEGIN
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
raw.c_oflag &= ~(OPOST);
raw.c_cflag |= (CS8);
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN); // | ISIG
raw.c_cc[VMIN] = 0;
raw.c_cc[VTIME] = 1;
// NOLINTEND

/* put terminal in raw mode after flushing */
if (tcsetattr(m_fd, TCSAFLUSH, &raw) < 0) {
throw error<error_code_t::TERMIOSWR>();
}
}

buffer(const buffer&) = delete;
buffer& operator=(const buffer&) = delete;

buffer(buffer&&) = default;
buffer& operator=(buffer&&) = default;

~buffer() { tcsetattr(m_fd, TCSAFLUSH, &m_orig_termios); }

uint8_t read()
{
if (m_start == m_end && get() == 0) {
return 0;
}

uint8_t chr = m_buffer[m_start]; // NOLINT
m_start = (m_start + 1) % m_buffer.size();
return chr;
}

uint8_t read_blocking()
{
while (m_start == m_end) {
get();
}

uint8_t chr = m_buffer[m_start]; // NOLINT
m_start = (m_start + 1) % m_buffer.size();
return chr;
}

void flush()
{
while (get() != 0) {
}
}

private:
size_t get()
{
ssize_t scnt = -1;
if ((m_end + 1) % m_buffer.size() == m_start) {
throw error<error_code_t::BUFFULL>();
}

if (m_start <= m_end) {
scnt = ::read(m_fd, m_buffer.data() + m_end, m_buffer.size() - m_end);
} else {
scnt = ::read(m_fd, m_buffer.data() + m_end, m_start - m_end);
}

if (scnt == -1) {
throw error<error_code_t::CHARRD>();
}

const auto cnt = static_cast<size_t>(scnt);
m_end = (m_end + cnt) % m_buffer.size();
return cnt;
}

std::array<uint8_t, 1024> m_buffer = {0};
int m_fd = 0;

uint64_t m_start = 0;
uint64_t m_end = 0;

struct termios m_orig_termios;
};

inline auto& get_buffer()
{
static std::optional<buffer> ibuf;
return ibuf;
}

inline void init_buffer(int fdsc)
{
get_buffer().emplace(fdsc);
}

inline void dest_buffer()
{
get_buffer().reset();
}

inline std::pair<std::uint16_t, std::uint16_t> get_screen_size()
{
#ifdef TIOCGSIZE
struct ttysize tts = {};
if (ioctl(STDIN_FILENO, TIOCGSIZE, &tts) == -1) { // NOLINT
throw error<error_code_t::IOCTL>();
}
return {tts.ts_cols, tts.ts_lines};
#elif defined(TIOCGWINSZ)
struct winsize tts = {};
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &tts) == -1) { // NOLINT
throw error<error_code_t::IOCTL>();
}
return {tts.ws_col, tts.ws_row};
#endif /* TIOCGSIZE */

throw error<error_code_t::SCREENSZ>();
}

class event
{
public:
enum class Type : std::uint8_t
{
NONE = 0,
KEY = 1,
RESIZE = 2,
MOUSE = 3,
};

enum class Mod : std::uint8_t
{
ALT = 1,
CTRL = 2,
SHIFT = 4,
MOTION = 8,
};

event(Type type, uint8_t mod_mask, uint8_t key) // NOLINT
: m_type(type)
, m_mod_mask(mod_mask)
, m_key(key)
{
}

const auto& type() const { return m_type; }
auto& type() { return m_type; }

const auto& key() const { return m_key; }
auto& key() { return m_key; }

const auto& mod_mask() const { return m_mod_mask; }
auto& mod_mask() { return m_mod_mask; }

bool is_set(uint8_t mask) const { return mask == (m_mod_mask & mask); }

private:
Type m_type = Type::NONE;
uint8_t m_mod_mask = 0;
uint8_t m_key = 0;
};

inline event get_event()
{
const auto chr = get_buffer().value().read();
return {chr != 0 ? event::Type::KEY : event::Type::NONE, 0, chr};
}

} // namespace alec

diff --git a/ source/driver.hpp b/ source/driver.hpp

@@ -1,38 +0,0 @@

#pragma once

#include <string>

#define yyFlexLexer yy_alec_FlexLexer
#if !defined(yyFlexLexerOnce)
# include <FlexLexer.h>
#endif

#include "location.hpp"
#include "parser.hpp"

namespace alec
{

class driver : public yy_alec_FlexLexer
{
int m_current_line = 0;

parser::semantic_type* m_yylval = nullptr;
location_t* m_yylloc = nullptr;

void copy_location()
{
*m_yylloc = location_t(m_current_line, m_current_line);
}

public:
driver(std::istream& ins, const bool debug)
: yy_alec_FlexLexer(&ins)
{
yy_alec_FlexLexer::set_debug(static_cast<int>(debug));
}

int yylex(parser::semantic_type* yylval, location_t* lloc);
};

} // namespace alec

diff --git a/ source/generator.cpp b/ source/generator.cpp

@@ -1,181 +0,0 @@

#include <cstring>
#include <fstream>
#include <iostream>
#include <set>
#include <span>
#include <string>
#include <vector>

#include "generator.h"

#include <cemplate/cemplate.hpp>

#include "driver.hpp"

namespace alec
{

extern std::vector<record> records; // NOLINT
extern std::vector<std::string> epilogue; // NOLINT
extern std::vector<std::string> prologue; // NOLINT

} // namespace alec

namespace
{

auto generate_dupes()
{
std::set<std::string> dupes;
std::set<std::string> seen;
for (const auto& record : alec::records) {
const auto [_, inserted] = seen.insert(record.name);
if (!inserted) {
dupes.insert(record.name);
}
}
return dupes;
}

void generate_variables()
{
using namespace cemplate; // NOLINT

Program prog(std::cout);

prog.comment("Template compile-time variables\n");
prog.comment("Forward-declare templates\n");

const auto dupes = generate_dupes();
for (const auto& dup : dupes) {
prog.template_decl({"auto... val"});
prog.declaration("static const char* const", dup + "_v", string(""));
prog.line_empty();
}

prog.comment("Template specializations\n");
for (const auto& record : alec::records) {
if (record.recipe.empty()) { // comment
std::cout << record.name << '\n';
continue;
}

std::vector<std::string> params;
params.reserve(record.args.size());
for (const auto& arg : record.args) {
params.emplace_back(arg.substr(arg.find(' ') + 1));
}

if (!record.args.empty()) {
prog.template_decl(record.args);
}

if (!record.rules.empty()) {
prog.require(join(std::begin(params),
std::end(params),
" && ",
[&](const auto& param)
{
return join(
std::begin(record.rules),
std::end(record.rules),
" && ",
[&](const auto& rule)
{ return template_def(rule + "_v", {param}); });
}));
}

const auto var = record.name + "_v";

const auto type =
dupes.contains(record.name) ? template_def(var, params) : var;

const auto* temp = !record.recipe.empty() && record.recipe[0][0] == '"'
? "details::escape_literal"
: "details::escape";

prog.declaration(
"static constexpr auto", type, template_def(temp, record.recipe));

prog.line_empty();
}
}

void generate_functions()
{
using namespace cemplate; // NOLINT

Program prog(std::cout);

prog.comment("Run-time functions\n");
for (const auto& record : alec::records) {
if (record.recipe.empty()) { // comment
std::cout << record.name << '\n';
continue;
}

std::vector<std::string> params;
params.reserve(record.args.size());
for (const auto& arg : record.args) {
params.emplace_back(arg.substr(arg.find(' ') + 1));
}

prog.function_open(record.name, "static constexpr auto", record.args);

if (!record.rules.empty()) {
for (const auto& param : params) {
prog.call(
"assert",
join(std::begin(record.rules),
std::end(record.rules),
" && ",
[&](const std::string& rule) { return call(rule, {param}); }));
}
}

if (record.args.empty()) {
prog.ret(record.name + "_v");
} else {
prog.ret(call("details::helper::make", record.recipe));
}

prog.function_close(record.name);
}
}

} // namespace

int main(int argc, char* argv[])
{
const std::span args(argv, static_cast<std::size_t>(argc));

const bool debug = argc > 1 && std::strcmp(args[1], "--debug") == 0;
std::ifstream ifile;
if (argc != 1) {
ifile.open(args[!debug ? 1 : 2]);
}

using namespace alec; // NOLINT

driver drv = argc == 1 ? driver(std::cin, debug) : driver(ifile, debug);
parser parser(drv, debug);
const int res = parser();

if (res != 0) {
std::cerr << "Parser error";
return -1;
}

// print prologue section
for (const auto& line : prologue) {
std::cout << line;
}

generate_variables();
generate_functions();

// print epilogue section
for (const auto& line : epilogue) {
std::cout << line;
}
}

diff --git a/ source/generator.h b/ source/generator.h

@@ -1,19 +0,0 @@

#pragma once

#include <string>
#include <vector>

namespace alec
{

struct record
{
std::string name;
std::vector<std::string> args;
std::vector<std::string> rules;
std::vector<std::string> recipe;

bool operator<(const record& rhs) const { return name < rhs.name; }
};

} // namespace alec

diff --git a/ source/lexer.l b/ source/lexer.l

@@ -1,83 +0,0 @@

%{
#include <stack>
#include "driver.hpp"
#include "parser.hpp"

using namespace alec;

#undef YY_DECL
#define YY_DECL int driver::yylex(parser::semantic_type* yylval, location_t *const lloc)

#define YY_USER_INIT m_yylloc = lloc;
#define YY_USER_ACTION copy_location();

std::stack<int> mode_st;

#define BEGIN_MODE(mode) do { \
if(yy_flex_debug) std::cerr<<"Starting mode: "<<mode<<std::endl; \
mode_st.push(YY_START); \
BEGIN((mode)); \
} while(0);

#define END_MODE() do { \
if(yy_flex_debug) std::cerr<<"Returning to mode: "<<mode_st.top()<<std::endl; \
BEGIN(mode_st.top()); \
mode_st.pop(); \
} while(0);
%}

%option c++ noyywrap debug nodefault
%option yyclass = "driver"
%option prefix = "yy_alec_"


LINE_END (\n|\r|\r\n)
SWITCH_BEGIN "/\*%%\*//\*"{LINE_END}
SWITCH_END "\*//\*%%\*/"{LINE_END}

%x GEN LAST

%%

%{
using Token = parser::token;
%}


{SWITCH_BEGIN} { BEGIN_MODE(GEN); return Token::SWITCH; }
.*{LINE_END} {
yylval->emplace<std::string>(yytext);
return Token::PROLOGUE;
}

<GEN>{LINE_END} { return Token::EOL; }
<GEN>^[\t ]*{LINE_END} { return Token::EOL; }
<GEN>^[\t ]*\|*[\t ]*{LINE_END} { return Token::EMPTY; }

<GEN>^[\t ]*"//".* {
yylval->emplace<std::string>(yytext);
return Token::COMMENT;
}

<GEN>, { return Token::COMMA; }

<GEN>[^,\n]* {
char *p = yytext + strlen(yytext) - 1;
while(isspace(*p)) *p-- = '\0';
while(*yytext && isspace(*yytext)) yytext++;
yylval->emplace<std::string>(yytext);
return Token::LITERAL;
}

<GEN>{SWITCH_END} {
BEGIN_MODE(LAST);
return Token::SWITCH;
}

<LAST>.*{LINE_END} {
yylval->emplace<std::string>(yytext);
return Token::EPILOGUE;
}

%%

diff --git a/ source/location.hpp b/ source/location.hpp

@@ -1,18 +0,0 @@

#pragma once

#include <cstddef>
#include <ostream>
#include <utility>

namespace alec
{

using position_t = std::size_t;
using location_t = std::pair<std::size_t, std::size_t>;

} // namespace alec

inline std::ostream& operator<<(std::ostream& ost, const alec::location_t& loc)
{
return ost << "[" << loc.first << "-" << loc.second << "]";
}

diff --git a/ source/parser.y b/ source/parser.y

@@ -1,106 +0,0 @@

%require "3.8.2"
%language "c++"

%code requires {
#include <string>
#include <cstdint>
#include "location.hpp"
#include "generator.h"

namespace alec {
class driver;
} // namespace alec
}

%define api.namespace { alec }
%define api.parser.class { parser }
%define api.value.type variant
%define api.location.type { location_t }

%locations
%define parse.error detailed

%header
%verbose

%parse-param {driver &drv}
%parse-param {const bool debug}

%initial-action
{
#if YYDEBUG != 0
set_debug_level(debug);
#endif
};

%code {
#include "driver.hpp"

namespace alec {
template<typename RHS>
void calcLocation(location_t &current, const RHS &rhs, const std::size_t n);

std::vector<record> records;
std::vector<std::string> epilogue;
std::vector<std::string> prologue;
} // namespace alec

#define YYLLOC_DEFAULT(Cur, Rhs, N) calcLocation(Cur, Rhs, N)
#define yylex drv.yylex
}

%left <char *> LITERAL COMMENT PROLOGUE EPILOGUE
%token EOL COMMA SWITCH EMPTY

%type <record> record
%type <std::vector<std::string>> list items
%type <std::string> name

%start document

%%

document: prologue grammar epilogue

prologue: %empty
| prologue PROLOGUE { prologue.emplace_back($2); }
;

epilogue: SWITCH
| epilogue EPILOGUE { epilogue.emplace_back($2); }
;

grammar: SWITCH
| grammar EOL
| grammar record { records.emplace_back($2); }
;

record: name list list list { $$ = record($1, $2, $3, $4); }
| COMMENT { $$ = record($1, {}, {}, {}); }
;

name: LITERAL EOL { $$ = $1; }
;

list: EMPTY { $$ = {}; }
| items EOL { $$ = $1; }
;

items: LITERAL { $$ = { $1 }; }
| items COMMA LITERAL { $1.emplace_back($3); $$ = $1; }
;

%%

namespace alec {
template<typename RHS>
inline void calcLocation(location_t &current, const RHS &rhs, const std::size_t n)
{
current = location_t(YYRHSLOC(rhs, 0).first, YYRHSLOC(rhs, n).second);
}

void parser::error(const location_t &location, const std::string &message)
{
std::cerr << "Error at lines " << location << ": " << message << std::endl;
}
} // namespace alec