alec

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

commit58f5b24c1c58f2fa5a2c06d758735150261483df
parent73bcb13900667e4b56264b6d38eeaab42434aa2d
authorDimitrije Dobrota <mail@dimitrijedobrota.com>
dateMon, 26 Feb 2024 22:42:53 +0000

Proof of concept generator in Bison and Flex Since all of the logic is duplicated I thought I should find a way to automate the code generation. Here I present an experimental version of the library generator, that's yet to be included in the building process, where I experiment with Bison and Flex, by parsing a configuration file and generating template and function code automatically. Documentations will be added after intervace and tool have been refined.

Diffstat:
MCMakeLists.txt|++++++--
Msrc/CMakeLists.txt|+++++++++++++++++++++++++
Asrc/generator.h|++++++++++++++++++++++++++++++++
Asrc/lexer.l|+++++++++++++++++++++
Asrc/parser.y|+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/rules|+++++++++++++++++++++++++++++

6 files changed, 316 insertions(+), 2 deletions(-)


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

@@ -3,13 +3,17 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

project(
Alec
VERSION 0.0.8
VERSION 0.0.9
DESCRIPTION "Abstraction Layer for Escape Codes"
HOMEPAGE_URL https://git.dimitrijedobrota.com/alec.git
LANGUAGES CXX
LANGUAGES C CXX
)
enable_testing()
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS YES)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

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

@@ -1,3 +1,28 @@

set(PARSER_DIR "${CMAKE_CURRENT_BINARY_DIR}")
find_package(FLEX)
find_package(BISON)
set(LEXER_OUT "${PARSER_DIR}/lexer.c")
set(PARSER_OUT "${PARSER_DIR}/parser.c")
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(FLAGS "--debug")
endif()
FLEX_TARGET(LEXER lexer.l "${LEXER_OUT}" DEFINES_FILE "${PARSER_DIR}/scanner.h" COMPILE_FLAGS "${FLAGS}")
BISON_TARGET(PARSER parser.y "${PARSER_OUT}" DEFINES_FILE "${PARSER_DIR}/parser.h" COMPILE_FLAGS "${FLAGS}")
ADD_FLEX_BISON_DEPENDENCY(LEXER PARSER)
add_executable(generator "${LEXER_OUT}" "${PARSER_OUT}")
target_include_directories(generator PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}")
set_target_properties(generator PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
add_library(alec INTERFACE)
target_include_directories(alec INTERFACE .)

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

@@ -0,0 +1,32 @@

#ifndef ALEC_PARSER_H
#define ALEC_PARSER_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern int yylineno;
void yyerror(char *s, ...);
struct list {
char *data;
struct list *next;
};
struct record {
char *name;
struct list *args;
struct list *rules;
struct list *recipe;
};
struct list *list_new(char *data, struct list *list);
void list_free(struct list *l, void (*free_data)(void *));
struct record *record_new(char *name, struct list *args, struct list *rules, struct list *recipe);
void record_free(void *rp);
void record_print_function(const struct record *r);
void record_print_template(const struct record *r);
#endif

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

@@ -0,0 +1,21 @@

%option noyywrap nodefault yylineno
%{
#include "generator.h"
#include "parser.h"
#include <ctype.h>
%}
LINE_END (\n|\r|\r\n)
%%
{LINE_END} { return EOL; }
, { return COMMA; }
[^,\n]* {
while(*yytext && isspace(*yytext)) yytext++;
yylval.n = strdup(yytext); return LITERAL;
}
%%

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

@@ -0,0 +1,203 @@

%{
#include "generator.h"
#include <stdarg.h>
int yylex(void);
int yyparse(void);
void yyrestart(FILE *);
int yylex_destroy(void);
struct list *records;
%}
%union {
struct record *r;
struct list *l;
char *n;
}
%token <n> LITERAL
%token EOL COMMA
%type <r> record
%type <l> list
%type <n> name
%start document
%%
document: record { records = list_new((char *)$1, records); }
| record EOL document { records = list_new((char *)$1, records); }
record: name list list list { $$ = record_new($1, $2, $3, $4); }
name: LITERAL EOL { $$ = $1; }
list: EOL { $$ = NULL; }
| LITERAL EOL { $$ = list_new($1, NULL); }
| LITERAL COMMA list { $$ = list_new($1, $3); }
%%
#ifdef YYDEBUG
int yydebug = 1;
#endif
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);
}
for(struct list *p = records; p; p = p->next) {
record_print_function((const struct record *)p->data);
}
for(struct list *p = records; p; p = p->next) {
record_print_template((const struct record *)p->data);
}
list_free(records, record_free);
yylex_destroy();
}
struct list *list_new(char *data, struct list *list) {
struct list *l = malloc(sizeof(struct list));
if(!l) {
yyerror("out of space");
exit(1);
}
*l = (struct list) {
.data = data,
.next = list,
};
return l;
}
void list_free(struct list *l, void (*free_data)(void *)) {
struct list *t;
while(l) {
t = l;
l = l->next;
if(free_data) free_data((void *)t->data);
free(t);
}
}
struct record *record_new(char *name, struct list *args, struct list *rules, struct list *recipe){
struct record* rec = malloc(sizeof(struct record));
if(!rec) {
yyerror("out of space");
exit(1);
}
*rec = (struct record) {
.name = name,
.args = args,
.rules = rules,
.recipe = recipe,
};
return rec;
}
void record_free(void *rp) {
struct record *r = (struct record *)rp;
list_free(r->args, free);
list_free(r->rules, free);
list_free(r->recipe, free);
free(r->name);
free(r);
}
void record_print_function(const struct record *r) {
struct list dummy, *c = &dummy;
printf("static constexpr auto %s(", r->name);
for(struct list *p = r->args; p; p = p->next) {
c = c->next = list_new(strchr(p->data, ' ') + 1, NULL);
printf("%s", p->data);
if(p->next) printf(", ");
}
printf(") {\n");
if(r->rules) {
for(struct list *p = dummy.next; p; p = p->next) {
printf("\t assert(");
for(struct list *q = r->rules; q; q = q->next) {
printf("%s(%s)", q->data, p->data);
if(q->next) printf(" && ");
}
printf(");\n");
}
}
printf("\treturn details::helper::make(");
for(struct list *p = r->recipe; p; p = p->next) {
printf("%s", p->data);
if(p->next) printf(", ");
}
printf(");\n}\n\n");
list_free(dummy.next, NULL);
}
void record_print_template(const struct record *r) {
struct list dummy, *c = &dummy;
printf("template <");
for(struct list *p = r->args; p; p = p->next) {
c = c->next = list_new(strchr(p->data, ' ') + 1, NULL);
printf("%s", p->data);
if(p->next) printf(", ");
}
printf(">\n");
if(r->rules) {
printf("\trequires ");
for(struct list *p = dummy.next; p; p = p->next) {
for(struct list *q = r->rules; 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\n\t = details::escape<", r->name);
for(struct list *p = r->recipe; p; p = p->next) {
printf("%s", p->data);
if(p->next) printf(", ");
}
printf(">;\n\n");
list_free(dummy.next, NULL);
}
void yyerror(char *s, ...) {
va_list ap;
va_start(ap, s);
fprintf(stderr, "%d: error: ", yylineno);
vfprintf(stderr, s, ap);
fprintf(stderr, "\n");
}

diff --git a/src/rules b/src/rules

@@ -0,0 +1,29 @@

foreground
int idx
limit_256
32, ';', 5, ';', idx, 'm'
background
int idx
limit_256
42, ';', 5, ';', idx, 'm'
foreground
int R, int G, int B
limit_256
38, ';', 5, ';', R, ';', G, ';', B, 'm'
background
int R, int G, int B
limit_256
38, ';', 5, ';', R, ';', G, ';', B, 'm'
foreground
COLOR color
(int)color + 30, 'm'
background
COLOR color
(int)color + 40, 'm'