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