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
Diffstat: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