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 | 52dc5de05fbb271b2d50973f71c58114e2d363ce | 
| parent | 6afe33840c7ae7d9a1c1a6fc265d9b962fafb7e7 | 
| author | Dimitrije Dobrota < mail@dimitrijedobrota.com > | 
| date | Thu, 30 Jan 2025 20:16:56 +0100 | 
Revrite lexer, parser and generator in CPP
| M | .clang-tidy | | | ++++++ - | 
| M | CMakeLists.txt | | | +++++++++++ ------- | 
| M | example/alec_compile.cpp | | | +++ --- | 
| M | example/alec_runtime.cpp | | | ++++ --- | 
| M | source/alec.rules.hpp | | | +++++++++++++++++++++++++++++ ------------------------------ | 
| A | source/driver.hpp | | | ++++++++++++++++++++++++++++++++++++++ | 
| D | source/generator.c | | | --------------------------------------------------------------------------------- | 
| A | source/generator.cpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| M | source/generator.h | | | +++++++++++++ ------------------------------------------------ | 
| M | source/lexer.l | | | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ----------------- | 
| A | source/location.hpp | | | ++++++++++++++++++ | 
| M | source/parser.y | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++ --------------------------- | 
12 files changed, 404 insertions(+), 407 deletions(-)
diff --git a/ .clang-tidy b/ .clang-tidy
@@ -5,14 +5,19 @@
Checks: "*,\
            -google-readability-todo,\
            -altera-*,\
            -cppcoreguidelines-avoid-magic-numbers,\
            -fuchsia-*,\
            fuchsia-multiple-inheritance,\
            -llvm-header-guard,\
            -llvm-include-order,\
            -llvmlibc-*,\
            -modernize-use-nodiscard,\
            -misc-non-private-member-variables-in-classes"
            -modernize-use-trailing-return-type,\
            -misc-non-private-member-variables-in-classes,
            -readability-magic-numbers
          "
          WarningsAsErrors: ''
          ExcludeHeaderFilterRegex: 'parser.hpp'
          CheckOptions:
            - key: 'bugprone-argument-comment.StrictMode'
              value: 'true'
        
        diff --git a/ CMakeLists.txt b/ CMakeLists.txt
          @@ -4,10 +4,10 @@ 
          include(cmake/prelude.cmake)
        
        
          project(
              alec
              VERSION 0.1.9
              VERSION 0.1.10
              DESCRIPTION "Abstraction Layer for Escape Codes"
              HOMEPAGE_URL "git://git.dimitrijedobrota.com/alec.git"
              LANGUAGES C CXX
              LANGUAGES CXX
          )
          include(cmake/project-is-top-level.cmake)
        
        
          @@ -21,19 +21,23 @@ 
          file(MAKE_DIRECTORY ${PARSER_DIR})
        
        
          find_package(FLEX)
          find_package(BISON)
          set(LEXER_OUT "${PARSER_DIR}/lexer.c")
          set(PARSER_OUT "${PARSER_DIR}/parser.c")
          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.h" COMPILE_FLAGS "${FLAGS}")
          BISON_TARGET(PARSER source/parser.y "${PARSER_OUT}" DEFINES_FILE "${PARSER_DIR}/parser.h" COMPILE_FLAGS "${FLAGS}")
          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)
          add_executable(generator "${LEXER_OUT}" "${PARSER_OUT}" source/generator.c)
          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})
          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")
        
        diff --git a/ example/alec_compile.cpp b/ example/alec_compile.cpp
@@ -1,12 +1,12 @@
#include "alec/alec.hpp"
          #include <iostream>
          using namespace alec;
          using namespace alec; // NOLINT
          using enum Color;
          using enum Decor;
          int main(void) {
          int main() {
              std::cout << abuf_enable_v << cursor_hide_v;
              std::cout << cursor_position_v<1, 1> << foreground_v<91> << "HELLO!\n";
        
        
          @@ -21,7 +21,7 @@ 
          int main(void) {
        
        
              std::cout << cursor_down_v<10> << "General Kenobi!";
              std::cout << cursor_position_v<10, 40> << "no pain no gain" << cursor_restore_v << cursor_show_v;
              getchar();
              (void) getchar();
              std::cout << abuf_disable_v;
          diff --git a/ example/alec_runtime.cpp b/ example/alec_runtime.cpp
@@ -1,11 +1,12 @@
#include "alec/alec.hpp"
          #include <iostream>
          using namespace alec;
          using namespace alec; // NOLINT
          using enum Color;
          using enum Decor;
          int main(void) {
          int main() {
              std::cout << abuf_enable() << cursor_hide();
              std::cout << cursor_position(1, 1) << foreground(91) << "HELLO!\n";
        
        
          @@ -20,7 +21,7 @@ 
          int main(void) {
        
        
              std::cout << cursor_down(10) << "General Kenobi!";
              std::cout << cursor_position(10, 40) << "no pain no gain" << cursor_restore() << cursor_show();
              getchar();
              (void) getchar();
              std::cout << abuf_disable();
          diff --git a/ source/alec.rules.hpp b/ source/alec.rules.hpp
@@ -1,9 +1,8 @@
#ifndef ALEC_ALEC_H
          #define ALEC_ALEC_H
          #pragma once
          #include <algorithm>
          #include <array>
          #include <assert.h>
          #include <cassert>
          #include <cstdint>
          #include <string>
          #include <type_traits>
        
        
          @@ -67,11 +66,13 @@ 
          struct string_literal
        
        
          };
          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; }
              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) len++;
                  while ((val /= 10) != 0) { 
          			len++;
          		}
                  return len;
              }
          
          @@ -88,12 +89,12 @@ 
          namespace helper {
        
        
              static constexpr char *append(char *ptr, int val) {
                  char *tmp = ptr += size(val);
                  do {
                      *--tmp = '0' + (val % 10);
                  } while (val /= 10);
                      *--tmp = '0' + static_cast<char>(val % 10);
                  } while ((val /= 10) != 0);
                  return ptr;
              }
              static const std::string make(auto... args) {
              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;
        
        
          @@ -111,7 +112,8 @@ 
          namespace helper {
        
        
                  }();
                  static constexpr auto data = value.data();
              };
          };
          } // namespace helper
          template <auto... Args> static constexpr auto escape = helper::escape_t<Args...>().data;
          template <details::string_literal... Strs> static constexpr auto escape_literal = escape<Strs...>;
        
        
          @@ -120,14 +122,14 @@ 
          template <details::string_literal... Strs> static constexpr auto escape_literal 
        
        
          // Tamplate parameter constraints
          template <int n>
          concept limit_256_v = n >= 0 && n < 256;
          template <int N>
          concept limit_256_v = N >= 0 && N < 256;
          template <int n>
          concept limit_pos_v = n >= 0;
          template <int N>
          concept limit_pos_v = N >= 0;
          static inline bool limit_pos(int n) { return n >= 0; };
          static inline bool limit_256(int n) { return n >= 0 && n < 256; };
          static constexpr bool limit_pos(int n) { return n >= 0; }
          static constexpr bool limit_256(int n) { return n >= 0 && n < 256; }
          %%
          
          @@ -177,12 +179,12 @@ 
          static inline bool limit_256(int n) { return n >= 0 && n < 256; };
        
        
              erase_display
              Motion m
              |
              (int)m, 'J'
              static_cast<int>(m), 'J'
              erase_line
              Motion m
              |
              (int)m, 'K'
              static_cast<int>(m), 'K'
          // Scroll up/down
          
          @@ -210,12 +212,12 @@ 
          static inline bool limit_256(int n) { return n >= 0 && n < 256; };
        
        
              foreground
              Color color
              |
              (int)color + 30, 'm'
              static_cast<int>(color) + 30, 'm'
              background
              Color color
              |
              (int)color + 40, 'm'
              static_cast<int>(color) + 40, 'm'
          // 256-color palette
          
          @@ -232,26 +234,26 @@ 
          static inline bool limit_256(int n) { return n >= 0 && n < 256; };
        
        
          // RGB colors
              foreground
              int R, int G, int B
              int red, int green, int blue
              limit_256
              38, ';', 2, ';', R, ';', G, ';', B, 'm'
              38, ';', 2, ';', red, ';', green, ';', blue, 'm'
              background
              int R, int G, int B
              int red, int green, int blue
              limit_256
              48, ';', 2, ';', R, ';', G, ';', B, 'm'
              48, ';', 2, ';', red, ';', green, ';', blue, 'm'
          // Set/reset text decorators
              decor_set
              Decor decor
              |
              (int)decor, 'm'
              static_cast<int>(decor), 'm'
              decor_reset
              Decor decor
              |
              (int)decor + 20, 'm'
              static_cast<int>(decor) + 20, 'm'
          // Save/restore cursor position;
          
          @@ -331,7 +333,4 @@ 
          static inline bool limit_256(int n) { return n >= 0 && n < 256; };
        
        
          // Keyboard string TODO
          } // namespace ALEC
          #endif
          } // namespace alec
        
        diff --git a/ source/driver.hpp b/ source/driver.hpp
@@ -0,0 +1,38 @@
#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.c b/ source/generator.c
@@ -1,265 +0,0 @@
#include "generator.h"
          #include "parser.h"
          #define MALLOC(x)                                                                                            \
              do {                                                                                                     \
                  x = malloc(sizeof(*x));                                                                              \
                  if (!x) yyerror("out of space"), exit(1);                                                            \
              } while (0);
          #ifndef YYDEBUG
          int yydebug = 1;
          #endif
          list_t records = {0};
          list_t epilogue = {0};
          list_t prologue = {0};
          int main(const int argc, char *argv[]) {
              if (argc < 2) {
                  yyparse();
                  return 0;
              }
              for (int i = 1; i < argc; i++) {
                  FILE *f = fopen(argv[i], "r");
                  if (!f) {
                      perror(argv[1]);
                      return -1;
                  }
                  yyrestart(f);
                  yyparse();
                  fclose(f);
              }
              // print prologue section
              for (node_t *p = prologue.head; p; p = p->next) {
                  printf("%s", p->data);
              }
              list_t dupes = {0};
              record_dupes(&dupes, &records);
              printf("\n/* Template compile-time variables */\n\n");
              record_print_dupes(&dupes);
              for (node_t *p = records.head; p; p = p->next) {
                  const record_t *r = (const record_t *)p->data;
                  record_print_template(r, list_find(&dupes, r->name, scmp));
              }
              printf("\n/* Run-time functions */\n\n");
              for (node_t *p = records.head; p; p = p->next) {
                  const record_t *r = (const record_t *)p->data;
                  record_print_function(r);
              }
              // print epilogue section
              for (node_t *p = epilogue.head; p; p = p->next) {
                  printf("%s", p->data);
              }
              list_free(&dupes, 0);
              list_free(&prologue, free);
              list_free(&records, record_free);
              list_free(&epilogue, free);
              yylex_destroy();
          }
          node_t *node_new(char *data) {
              node_t *n;
              MALLOC(n);
              *n = (node_t){
                  .data = data,
                  .next = NULL,
              };
              return n;
          }
          list_t *list_new(char *data) {
              list_t *l;
              MALLOC(l);
              *l = (list_t){0};
              if (data) list_append(l, node_new(data));
              return l;
          }
          void list_free(list_t *l, void (*free_data)(void *)) {
              if (!l) return;
              node_t *c = l->head, *t;
              while (c) {
                  t = c;
                  c = c->next;
                  if (free_data) free_data((void *)t->data);
                  free(t);
              }
          }
          void list_append(list_t *l, node_t *n) {
              if (!l->head) l->head = l->tail = n;
              else
                  l->tail = l->tail->next = n;
          }
          record_t *record_new(char *name, list_t *args, list_t *rules, list_t *recipe) {
              record_t *rec;
              MALLOC(rec);
              *rec = (record_t){
                  .name = name,
                  .args = args,
                  .rules = rules,
                  .recipe = recipe,
              };
              return rec;
          }
          void record_free(void *rp) {
              record_t *r = (record_t *)rp;
              if (r->args) list_free(r->args, free), free(r->args);
              if (r->rules) list_free(r->rules, free), free(r->rules);
              if (r->recipe) list_free(r->recipe, free), free(r->recipe);
              free(r->name);
              free(r);
          }
          int list_find(list_t *l, void *data, cmp_f cmp) {
              node_t *c = l->head;
              while (c) {
                  if (!cmp(c->data, data)) return 1;
                  c = c->next;
              }
              return 0;
          }
          void record_print_dupes(const list_t *l) {
              node_t *c = l->head;
              while (c) {
                  printf("template <auto... val> static const char *%s_v;\n", c->data);
                  c = c->next;
              }
              printf("\n");
          }
          void record_print_function(const record_t *r) {
              list_t dummy = {0};
              if (!r->recipe) { // comment
                  printf("%s\n\n", r->name);
                  return;
              }
              printf("static constexpr auto %s(", r->name);
              if (r->args) {
                  for (node_t *p = r->args->head; p; p = p->next) {
                      list_append(&dummy, node_new(strchr(p->data, ' ') + 1));
                      printf("%s", p->data);
                      if (p->next) printf(", ");
                  }
              }
              printf(") {\n");
              if (r->rules) {
                  for (node_t *p = dummy.head; p; p = p->next) {
                      printf("\tassert(");
                      for (node_t *q = r->rules->head; q; q = q->next) {
                          printf("%s(%s)", q->data, p->data);
                          if (q->next) printf(" && ");
                      }
                      printf(");\n");
                  }
              }
              if (r->args) {
                  printf("\treturn details::helper::make(");
                  for (node_t *p = r->recipe->head; p; p = p->next) {
                      printf("%s", p->data);
                      if (p->next) printf(", ");
                  }
                  printf(")");
              } else {
                  printf("\treturn %s_v", r->name);
              }
              printf(";\n}\n\n");
              list_free(&dummy, NULL);
          }
          void record_print_template(const record_t *r, int dup) {
              list_t dummy = {0};
              if (!r->recipe) { // comment
                  printf("%s\n\n", r->name);
                  return;
              }
              if (r->args) {
                  printf("template <");
                  for (node_t *p = r->args->head; p; p = p->next) {
                      list_append(&dummy, node_new(strchr(p->data, ' ') + 1));
                      printf("%s", p->data);
                      if (p->next) printf(", ");
                  }
                  printf(">\n");
              }
              if (r->rules) {
                  printf("\trequires ");
                  for (node_t *p = dummy.head; p; p = p->next) {
                      for (node_t *q = r->rules->head; q; q = q->next) {
                          printf("%s_v<%s>", q->data, p->data);
                          if (q->next) printf(" && ");
                      }
                      if (p->next) printf(" && ");
                  }
                  printf("\n");
              }
              printf("static constexpr auto %s_v", r->name);
              if (dup) {
                  printf("<");
                  for (node_t *p = dummy.head; p; p = p->next) {
                      printf("%s", p->data);
                      if (p->next) printf(", ");
                  }
                  printf(">");
              }
              if (r->recipe->head && r->recipe->head->data[0] == '"') {
                  printf("\n\t = details::escape_literal<%s>;", r->recipe->head->data);
              } else {
                  printf("\n\t = details::escape<");
                  for (node_t *p = r->recipe->head; p; p = p->next) {
                      printf("%s", p->data);
                      if (p->next) printf(", ");
                  }
                  printf(">;");
              }
              printf("\n\n");
              list_free(&dummy, NULL);
          }
          int scmp(const void *a, const void *b) { return strcmp((const char *)a, (const char *)b); }
          void record_dupes(list_t *d, list_t *l) {
              list_t s = {0};
              for (node_t *p = records.head; p; p = p->next) {
                  const record_t *r = (const record_t *)p->data;
                  if (!list_find(&s, r->name, scmp)) list_append(&s, node_new(r->name));
                  else if (!list_find(d, r->name, scmp))
                      list_append(d, node_new(r->name));
              }
              list_free(&s, NULL);
          }
        
        diff --git a/ source/generator.cpp b/ source/generator.cpp
@@ -0,0 +1,157 @@
#include <cstring>
          #include <format>
          #include <fstream>
          #include <iostream>
          #include <set>
          #include <string>
          #include <vector>
          #include "generator.h"
          #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
          template<typename T>
          std::string join(const std::vector<T>& vec, const std::string& delim)
          {
            std::string res;
            if (!vec.empty()) {
              res += vec[0];
              for (size_t idx = 1; idx < vec.size(); idx++) {
                res += delim + vec[idx];
              }
            }
            return res;
          }
          int main(const int argc, char* argv[])
          {
            using namespace alec;  // NOLINT
            const bool debug = argc > 1 && std::strcmp(argv[1], "--debug") == 0;
            std::ifstream ifile;
            if (argc != 1) {
              ifile.open(argv[!debug ? 1 : 2]);
            }
            driver drv = argc == 1 ? driver(std::cin, debug) : driver(ifile, debug);
            parser parser(drv, debug);
            const int res = parser();
            // print prologue section
            for (const auto line : prologue) {
              std::cout << line;
            }
            std::set<std::string> seen, dupes;
            for (const auto& record : records) {
              const auto [_, inserted] = seen.insert(record.name);
              if (!inserted) {
                dupes.insert(record.name);
              }
            }
            std::cout << "\n/* Template compile-time variables */\n\n";
            for (const auto& dup : dupes) {
              std::cout << std::format(
                  "template <auto... val> static const char *{}_v;\n", dup);
            }
            for (const auto& record : records) {
              if (record.recipe.empty()) {
                // comment
                std::cout << std::format("{}\n\n", record.name);
                continue;
              }
              std::vector<std::string> params(record.args.size());
              std::transform(record.args.begin(),
                             record.args.end(),
                             params.begin(),
                             [](const std::string& str)
                             { return str.substr(str.find(' ') + 1); });
              if (!record.args.empty()) {
                std::cout << std::format("template <{}>\n", join(record.args, ", "));
              }
              if (!record.rules.empty()) {
                std::vector<std::string> constraints;
                for (const auto& param : params) {
                  for (const auto& rule : record.rules) {
                    constraints.emplace_back(std::format("{}_v<{}>", rule, param));
                  }
                }
                std::cout << std::format("\trequires {}\n", join(constraints, " && "));
              }
              std::cout << std::format("static constexpr auto {}_v", record.name);
              if (dupes.contains(record.name)) {
                std::cout << std::format("<{}>", join(params, ", "));
              }
              if (!record.recipe.empty() && record.recipe[0][0] == '"') {
                std::cout << std::format("\n\t = details::escape_literal<{}>;\n\n",
                                         record.recipe[0]);
              } else {
                std::cout << std::format("\n\t = details::escape<{}>;\n\n",
                                         join(record.recipe, ", "));
              }
            }
            std::cout << "\n/* Run-time functions */\n\n";
            for (const auto& record : records) {
              if (record.recipe.empty()) {
                // comment
                std::cout << std::format("{}\n\n", record.name);
                continue;
              }
              std::vector<std::string> params(record.args.size());
              std::transform(record.args.begin(),
                             record.args.end(),
                             params.begin(),
                             [](const std::string& str)
                             { return str.substr(str.find(' ') + 1); });
              std::cout << std::format("static constexpr auto {}({}) {{\n",
                                       record.name,
                                       join(record.args, ", "));
              if (!record.rules.empty()) {
                for (const auto& param : params) {
                  std::vector<std::string> constraints;
                  for (const auto& rule : record.rules) {
                    constraints.emplace_back(std::format("{}({})", rule, param));
                  }
                  std::cout << std::format("\tassert({});\n", join(constraints, " && "));
                }
              }
              if (record.args.empty()) {
                std::cout << std::format("\treturn {}_v;", record.name);
              } else {
                std::cout << std::format("\treturn details::helper::make({});",
                                         join(record.recipe, ", "));
              }
              std::cout << "\n}\n\n";
            }
            // print epilogue section
            for (const auto line : epilogue) {
              std::cout << line;
            }
          }
        
        diff --git a/ source/generator.h b/ source/generator.h
@@ -1,54 +1,19 @@
#ifndef ALEC_PARSER_H
          #define ALEC_PARSER_H
          #pragma once
          #include <stdio.h>
          #include <stdlib.h>
          #include <string.h>
          #include <string>
          #include <vector>
          extern int yylineno;
          void yyerror(char *s, ...);
          namespace alec
          {
          typedef struct node node_t;
          typedef struct list list_t;
          typedef struct record record_t;
          struct record
          {
            std::string name;
            std::vector<char*> args;
            std::vector<char*> rules;
            std::vector<char*> recipe;
          struct node {
              char *data;
              node_t *next;
            bool operator<(const record& rhs) const { return name < rhs.name; }
          };
          struct list {
              node_t *head;
              node_t *tail;
          };
          struct record {
              char *name;
              list_t *args;
              list_t *rules;
              list_t *recipe;
          };
          typedef void (*free_f)(void *);
          typedef int (*cmp_f)(const void *, const void *);
          int scmp(const void *a, const void *b);
          node_t *node_new(char *data);
          list_t *list_new(char *data);
          void list_free(list_t *l, free_f free_data);
          void list_append(list_t *l, node_t *n);
          int list_find(list_t *l, void *data, cmp_f cmp);
          struct record *record_new(char *name, list_t *args, list_t *rules, list_t *recipe);
          void record_free(void *rp);
          void record_dupes(list_t *d, list_t *l);
          void record_print_template(const struct record *r, int dup);
          void record_print_function(const struct record *r);
          void record_print_dupes(const list_t *l);
          #endif
          }  // namespace alec
        
        diff --git a/ source/lexer.l b/ source/lexer.l
@@ -1,40 +1,81 @@
%option noyywrap nodefault yylineno
          %{
              #include "generator.h"
              #include "parser.h"
              #include <ctype.h>
              #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)
          %x GEN
          %x LAST
          %x GEN LAST
          %%
          %{
              using Token = parser::token;
          %}
          "%%"{LINE_END}     { BEGIN GEN; return SWITCH; }
          .*{LINE_END}       { yylval.n = strdup(yytext); return PROLOGUE; }
          "%%"{LINE_END}     { BEGIN_MODE(GEN); return Token::SWITCH; }
          .*{LINE_END}       { 
              yylval->emplace<char *>(strdup(yytext)); 
              return Token::PROLOGUE; 
          }
          <GEN>{LINE_END}                  { return EOL; }
          <GEN>^[\t ]*{LINE_END}           { return EOL; }
          <GEN>^[\t ]*\|*[\t ]*{LINE_END}  { return EMPTY; }
          <GEN>{LINE_END}                  { return Token::EOL; }
          <GEN>^[\t ]*{LINE_END}           { return Token::EOL; }
          <GEN>^[\t ]*\|*[\t ]*{LINE_END}  { return Token::EMPTY; }
          <GEN>^[\t ]*"//".* { yylval.n = strdup(yytext); return COMMENT; }
          <GEN>^[\t ]*"//".* {
              yylval->emplace<char *>(strdup(yytext));
              return Token::COMMENT;
          }
          <GEN>, { return COMMA; }
          <GEN>, { return Token::COMMA; }
          <GEN>[^,\n]*                  {
              char *p = yytext + strlen(yytext) - 1;
              while(isspace(*p)) *p-- = '\0';
          	while(*yytext && isspace(*yytext)) yytext++;
          	yylval.n = strdup(yytext); return LITERAL;
          	yylval->emplace<char *>(strdup(yytext));
              return Token::LITERAL;
          }
          <GEN>"%%"{LINE_END}        { BEGIN LAST; return SWITCH; }
          <GEN>"%%"{LINE_END} {
              BEGIN_MODE(LAST);
              return Token::SWITCH;
          }
          <LAST>.*{LINE_END}       { yylval.n = strdup(yytext); return EPILOGUE; }
          <LAST>.*{LINE_END} {
              yylval->emplace<char *>(strdup(yytext));
              return Token::EPILOGUE;
          }
          %%
          diff --git a/ source/location.hpp b/ source/location.hpp
@@ -0,0 +1,18 @@
#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,29 +1,60 @@
%require "3.8.2"
          %language "c++"
          %code requires {
              #include <string>
              #include <cstdint>
              #include "location.hpp"
              #include "generator.h"
              #include <stdarg.h>
              namespace alec {
                  class driver;
              } // namespace alec
          }
          %define api.value.type union
          %token <char *> n LITERAL COMMENT PROLOGUE EPILOGUE
          %define api.namespace { alec }
          %define api.parser.class { parser }
          %define api.value.type variant
          %define api.location.type { location_t }
          %type <record_t *> record
          %type <list_t *> list items
          %type <char *> name
          %locations
          %define parse.error detailed
          %token EOL COMMA SWITCH EMPTY
          %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"
          %code provides {
          	int yylex(void);
          	int yyparse(void);
          	void yyrestart(FILE *);
          	int yylex_destroy(void);
              namespace alec {
                  template<typename RHS>
                  void calcLocation(location_t ¤t, const RHS &rhs, const std::size_t n);
          	extern list_t records;
          	extern list_t epilogue;
          	extern list_t prologue;
                  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
          }
          %destructor { free($$); } <char *>
          %left <char *> LITERAL COMMENT PROLOGUE EPILOGUE
          %token EOL COMMA SWITCH EMPTY
          %type <record> record
          %type <std::vector<char*>> list items
          %type <char *> name
          %start document
          @@ -32,41 +63,44 @@
document: prologue grammar epilogue
          prologue: %empty
                | prologue PROLOGUE { list_append(&prologue, node_new($2)); }
                | prologue PROLOGUE { prologue.push_back($2); }
                ;
          epilogue: SWITCH
               | epilogue EPILOGUE { list_append(&epilogue, node_new($2)); }
               | epilogue EPILOGUE { epilogue.push_back($2); }
               ;
          grammar: SWITCH
             | grammar EOL
             | grammar record { list_append(&records, node_new((char *)$2)); }
             | grammar record { records.push_back(std::move($2)); }
             ;
          record: name list list list { $$ = record_new($1, $2, $3, $4); }
                | COMMENT             { $$ = record_new($1, NULL, NULL, NULL); }
          record: name list list list { $$ = record($1, $2, $3, $4); }
                | COMMENT             { $$ = record($1, {}, {}, {}); }
                ;
          name: LITERAL EOL   { $$ = $1; }
              ;
          list: EMPTY       { $$ = NULL; }
          list: EMPTY       { $$ = {}; }
              | items EOL   { $$ = $1; }
              ;
          items: LITERAL             { $$ = list_new($1); }
               | items COMMA LITERAL { list_append($1, node_new($3)); $$ = $1; }
          items: LITERAL             { $$ = { $1 }; }
               | items COMMA LITERAL { $1.push_back($3); $$ = $1; }
               ;
          %%
          void yyerror(char *s, ...) {
            va_list ap;
            va_start(ap, s);
            fprintf(stderr, "%d: error: ", yylineno);
            vfprintf(stderr, s, ap);
            fprintf(stderr, "\n");
          }
          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