alec

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

commit 58f5b24c1c58f2fa5a2c06d758735150261483df
parent 73bcb13900667e4b56264b6d38eeaab42434aa2d
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date:   Mon, 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 | 8++++++--
Msrc/CMakeLists.txt | 25+++++++++++++++++++++++++
Asrc/generator.h | 32++++++++++++++++++++++++++++++++
Asrc/lexer.l | 21+++++++++++++++++++++
Asrc/parser.y | 203+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/rules | 29+++++++++++++++++++++++++++++
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'