| alecAbstraction 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...
| 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 ¤t, 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 ¤t, 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