commit 3aaf690adcd8fbbbcfa5ff37d9bb47df6995b550
parent dfe8ff098c8cd88c717cb7f3bf57986a3d20ffbd
Author: nibo <nibo@relim.de>
Date: Fri, 11 Apr 2025 11:57:50 +0200
WIP: Start implementing grid sections
Diffstat:
2 files changed, 339 insertions(+), 16 deletions(-)
diff --git a/src/chordpro.c b/src/chordpro.c
@@ -29,6 +29,7 @@ static const char *state_enums[] = {
"STATE_CHORD",
"STATE_ANNOTATION",
"STATE_TAB",
+ "STATE_GRID",
"STATE_MARKUP_TAG",
"STATE_MARKUP_TAG_START",
"STATE_MARKUP_TAG_END",
@@ -4582,6 +4583,247 @@ cho_directive_label_parse(struct ChoContext *ctx, const char *directive_name, co
return label_name;
}
+static struct Attr **
+cho_attrs_parse(struct ChoContext *ctx, const char *str)
+{
+ const char *directive_name = "something";
+ struct Attr **attrs = NULL;
+ int a = 0;
+ enum OptionState state = OS_NAME;
+ enum AttrValueSyntax avs = AVS_UNINITIALIZED;
+ char name[5+1];
+ char value[URL_MAX_LEN+1];
+ int n = 0;
+ int v = 0;
+ memset(name, 0, sizeof(name));
+ memset(value, 0, sizeof(value));
+ const char *c;
+ for (c = str; *c; c++) {
+ switch (state) {
+ case OS_NAME:
+ if (is_whitespace(*c)) {
+ if (n == 0) {
+ break;
+ } else {
+ name[n] = 0;
+ cho_log(ctx, LOG_ERR, "Option with name '%s' in environment directive '%s' has no value.", name, directive_name);
+ return NULL;
+ }
+ }
+ if (*c == '=') {
+ name[n] = 0;
+ state = OS_VALUE;
+ break;
+ }
+ if (n > 4) {
+ cho_log(ctx, LOG_ERR, "Option name in environment directive '%s' is too long.", directive_name);
+ return NULL;
+ }
+ name[n] = *c;
+ n++;
+ break;
+ case OS_VALUE:
+ if (avs == AVS_UNINITIALIZED) {
+ if (is_whitespace(*c)) {
+ cho_log(ctx, LOG_ERR, "Whitespace character after equals sign in environment directive '%s' is invalid.", directive_name);
+ return NULL;
+ }
+ if (*c == '\'') {
+ avs = AVS_APOSTROPHE;
+ } else if (*c == '"') {
+ avs = AVS_QUOTATION_MARK;
+ } else {
+ avs = AVS_UNQUOTED;
+ value[v] = *c;
+ v++;
+ }
+ break;
+ }
+ if (*c == '\n') {
+ cho_log(ctx, LOG_ERR, "Newline character inside an option value in environment directive '%s' is invalid.", directive_name);
+ return NULL;
+ }
+ if (
+ (avs == AVS_APOSTROPHE && *c == '\'') ||
+ (avs == AVS_QUOTATION_MARK && *c == '"') ||
+ (avs == AVS_UNQUOTED && (*c == ' ' || *c == '\t'))
+ ) {
+ value[v] = 0;
+ attrs = erealloc(attrs, (a+1) * sizeof(struct Attr *));
+ attrs[a] = emalloc(sizeof(struct Attr));
+ attrs[a]->name = strdup(name);
+ attrs[a]->value = strdup(value);
+ a++;
+ memset(name, 0, n);
+ n = 0;
+ memset(value, 0, v);
+ v = 0;
+ avs = AVS_UNINITIALIZED;
+ state = OS_NAME;
+ break;
+ }
+ value[v] = *c;
+ v++;
+ break;
+ }
+ }
+ if (avs == AVS_UNQUOTED) {
+ value[v] = 0;
+ attrs = erealloc(attrs, (a+1) * sizeof(struct Attr *));
+ attrs[a] = emalloc(sizeof(struct Attr));
+ attrs[a]->name = strdup(name);
+ attrs[a]->value = strdup(value);
+ a++;
+ }
+ attrs = erealloc(attrs, (a+1) * sizeof(struct Attr *));
+ attrs[a] = NULL;
+ return attrs;
+}
+
+static char *
+cho_attrs_get(struct Attr **attrs, const char *name)
+{
+ struct Attr **a;
+ for (a = attrs; *a; a++) {
+ if (!strcmp((*a)->name, name)) {
+ return (*a)->value;
+ }
+ }
+ return NULL;
+}
+
+static bool
+cho_grid_shape_parse_and_set(struct ChoContext *ctx, const char *str)
+{
+ enum {
+ BEFORE_FIRST_PLUS,
+ AFTER_FIRST_PLUS,
+ AFTER_SECOND_PLUS,
+ AFTER_X
+ } state = BEFORE_FIRST_PLUS;
+ const char *c;
+ char tmp[6+1];
+ int t = 0;
+ int measures = 0;
+ int beats = 0;
+ int i;
+ for (c = str; *c; c++) {
+ switch (state) {
+ case BEFORE_FIRST_PLUS:
+ if (*c == '+') {
+ tmp[t] = 0;
+ i = atoi(tmp);
+ if (i == 0) {
+ LOG_DEBUG("atoi failed.");
+ return false;
+ }
+ ctx->grid_shape.left = i;
+ memset(tmp, 0, t);
+ t = 0;
+ state = AFTER_FIRST_PLUS;
+ break;
+ }
+ if (*c == 'x') {
+ tmp[t] = 0;
+ i = atoi(tmp);
+ if (i == 0) {
+ LOG_DEBUG("atoi failed.");
+ return false;
+ }
+ measures = i;
+ memset(tmp, 0, t);
+ t = 0;
+ state = AFTER_X;
+ break;
+ }
+ if (t > 5) {
+ cho_log(ctx, LOG_ERR, "");
+ }
+ tmp[t] = *c;
+ t++;
+ break;
+ case AFTER_FIRST_PLUS:
+ if (*c == '+') {
+ tmp[t] = 0;
+ i = atoi(tmp);
+ if (i == 0) {
+ LOG_DEBUG("atoi failed.");
+ return false;
+ }
+ ctx->grid_shape.cells = i;
+ memset(tmp, 0, t);
+ t = 0;
+ state = AFTER_SECOND_PLUS;
+ break;
+ }
+ if (*c == 'x') {
+ tmp[t] = 0;
+ i = atoi(tmp);
+ if (i == 0) {
+ LOG_DEBUG("atoi failed.");
+ return false;
+ }
+ measures = i;
+ memset(tmp, 0, t);
+ t = 0;
+ state = AFTER_X;
+ break;
+ }
+ tmp[t] = *c;
+ t++;
+ break;
+ case AFTER_SECOND_PLUS:
+ tmp[t] = *c;
+ t++;
+ break;
+ case AFTER_X:
+ if (*c == '+') {
+ tmp[t] = 0;
+ i = atoi(tmp);
+ if (i == 0) {
+ LOG_DEBUG("atoi failed.");
+ return false;
+ }
+ beats = i;
+ ctx->grid_shape.cells = measures * beats;
+ memset(tmp, 0, t);
+ t = 0;
+ state = AFTER_SECOND_PLUS;
+ break;
+ }
+ tmp[t] = *c;
+ t++;
+ break;
+ }
+ }
+ tmp[t] = 0;
+ i = atoi(tmp);
+ if (i == 0) {
+ LOG_DEBUG("atoi failed.");
+ return false;
+ }
+ switch (state) {
+ case BEFORE_FIRST_PLUS:
+ /* fallthrough */
+ case AFTER_FIRST_PLUS:
+ ctx->grid_shape.cells = i;
+ break;
+ case AFTER_SECOND_PLUS:
+ ctx->grid_shape.right = i;
+ break;
+ case AFTER_X:
+ beats = i;
+ ctx->grid_shape.cells = measures * beats;
+ break;
+ }
+ printf("grid shape: left %d right %d cells %d\n",
+ ctx->grid_shape.left,
+ ctx->grid_shape.right,
+ ctx->grid_shape.cells
+ );
+ return true;
+}
+
static bool
cho_context_init(
struct ChoContext *ctx,
@@ -4624,6 +4866,9 @@ cho_context_init(
ctx->line_no = 1;
ctx->tags = NULL;
ctx->image_assets = NULL;
+ ctx->grid_shape.cells = 8;
+ ctx->grid_shape.left = 1;
+ ctx->grid_shape.right = 1;
ctx->transpose_history = emalloc((ctx->th+1) * sizeof(int *));
ctx->transpose_history[ctx->th] = 0;
@@ -4648,6 +4893,7 @@ struct ChoSong **
cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *config)
{
enum AttrValueSyntax avs = AVS_UNINITIALIZED;
+ struct Attr **attrs;
struct ChoStyle *tag_style;
struct StyleProperty sprop;
struct ChoChord *tmp_chord;
@@ -4658,7 +4904,7 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c
struct ChoMetadata *metadata;
struct ChoLine ***lines;
struct ChoContext ctx;
- char *label, *metadata_value, *stripped_directive_value;
+ char *label, *metadata_value, *stripped_directive_value, *shape;
char directive_name[128];
char directive_value[4096];
char chord[15];
@@ -5043,9 +5289,14 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c
cho_log(&ctx, LOG_ERR, "Invalid directive '%s'.", directive_name);
return NULL;
}
- if (directive->stype == ST_TAB) {
+ switch (directive->stype) {
+ case ST_TAB:
ctx.state = STATE_TAB;
- } else {
+ break;
+ case ST_GRID:
+ ctx.state = STATE_GRID;
+ break;
+ default:
ctx.state = STATE_LYRICS;
}
cho_directive_free(directive);
@@ -5108,22 +5359,69 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c
ctx.songs[ctx.so]->sections = erealloc(ctx.songs[ctx.so]->sections, (ctx.se+1) * sizeof(struct ChoSection *));
ctx.songs[ctx.so]->sections[ctx.se] = cho_section_new();
ctx.songs[ctx.so]->sections[ctx.se]->type = directive->stype;
- ctx.songs[ctx.so]->sections[ctx.se]->label = emalloc(sizeof(struct ChoText));
+
if (strstr(directive_value, "=")) {
- label = cho_directive_label_parse(&ctx, directive_name, directive_value);
- if (!label) {
- LOG_DEBUG("cho_directive_label_parse failed.");
- cho_log(&ctx, LOG_ERR, "Failed to parse the section label. You have to ways of specifying a label:\n\t\t\t1. {start_of_*: label=\"Label name\"}\n\t\t\t2. {start_of*: Label name}");
+ attrs = cho_attrs_parse(&ctx, directive_value);
+ if (!attrs) {
+ LOG_DEBUG("cho_attrs_parse failed.");
return NULL;
}
- ctx.songs[ctx.so]->sections[ctx.se]->label->text = label;
+ label = cho_attrs_get(attrs, "label");
+ if (directive->stype == ST_GRID) {
+ shape = cho_attrs_get(attrs, "shape");
+ if (shape) {
+ if (!cho_grid_shape_parse_and_set(&ctx, shape)) {
+ LOG_DEBUG("cho_grid_parse_and_set_shape failed.");
+ return NULL;
+ }
+ }
+ }
} else {
- ctx.songs[ctx.so]->sections[ctx.se]->label->text = strdup(stripped_directive_value);
+ if (directive->stype == ST_GRID) {
+ // TODO: Parse the optional label name after the shape
+ if (!cho_grid_shape_parse_and_set(&ctx, stripped_directive_value)) {
+ LOG_DEBUG("cho_grid_parse_and_set_shape failed.");
+ return NULL;
+ }
+ } else {
+ label = stripped_directive_value;
+ }
}
- ctx.prev_ttype = ctx.current_ttype;
- ctx.current_ttype = TT_LABEL;
- ctx.songs[ctx.so]->sections[ctx.se]->label->style = cho_style_new_default(&ctx);
- ctx.current_ttype = ctx.prev_ttype;
+ if (label) {
+ ctx.songs[ctx.so]->sections[ctx.se]->label = emalloc(sizeof(struct ChoText));
+ ctx.songs[ctx.so]->sections[ctx.se]->label->text = strdup(label);
+ ctx.prev_ttype = ctx.current_ttype;
+ ctx.current_ttype = TT_LABEL;
+ ctx.songs[ctx.so]->sections[ctx.se]->label->style = cho_style_new_default(&ctx);
+ ctx.current_ttype = ctx.prev_ttype;
+ }
+ /* if (directive->stype == ST_GRID) {
+ if (strstr(directive_value, "=")) {
+ struct Attr **the_attrs = cho_attrs_parse(&ctx, directive_value);
+ struct Attr **tala;
+ for (tala = the_attrs; *tala; tala++) {
+ printf("attr %s = %s\n", (*tala)->name, (*tala)->value);
+ }
+ if (!the_attrs) {
+ LOG_DEBUG("cho_attrs_parse failed.");
+ return NULL;
+ }
+ cho_tag_attrs_free(the_attrs);
+ } else {
+ }
+ } else {
+ if (strstr(directive_value, "=")) {
+ label = cho_directive_label_parse(&ctx, directive_name, directive_value);
+ if (!label) {
+ LOG_DEBUG("cho_directive_label_parse failed.");
+ cho_log(&ctx, LOG_ERR, "Failed to parse the section label. You have to ways of specifying a label:\n\t\t\t1. {start_of_*: label=\"Label name\"}\n\t\t\t2. {start_of*: Label name}");
+ return NULL;
+ }
+ ctx.songs[ctx.so]->sections[ctx.se]->label->text = label;
+ } else {
+ ctx.songs[ctx.so]->sections[ctx.se]->label->text = strdup(stripped_directive_value);
+ }
+ } */
if (ctx.directive_has_tag) {
cho_style_complement(ctx.songs[ctx.so]->sections[ctx.se]->label->style, ctx.tags[ctx.ta]->style, &ctx.tags[ctx.ta]->style_presence);
ctx.directive_has_tag = false;
@@ -5449,9 +5747,14 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c
cho_log(&ctx, LOG_ERR, "Invalid directive type '%d'.", directive->dtype);
return NULL;
}
- if (directive->stype == ST_TAB) {
+ switch (directive->stype) {
+ case ST_TAB:
ctx.state = STATE_TAB;
- } else {
+ break;
+ case ST_GRID:
+ ctx.state = STATE_GRID;
+ break;
+ default:
ctx.state = STATE_LYRICS;
}
memset(directive_value, 0, strlen(directive_value));
@@ -5729,6 +6032,18 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c
ctx.te++;
break;
}
+ case STATE_GRID: {
+ if (prev_c == '\n' && c == '#') {
+ ctx.state_before_comment = STATE_GRID;
+ ctx.state = STATE_COMMENT;
+ break;
+ }
+ if (prev_c == '\n' && c == '{') {
+ ctx.state = STATE_DIRECTIVE_NAME;
+ break;
+ }
+ break;
+ }
case STATE_MARKUP_TAG: {
if (c == '/') {
ctx.state = STATE_MARKUP_TAG_END;
diff --git a/src/chordpro.h b/src/chordpro.h
@@ -84,6 +84,7 @@ enum State {
STATE_CHORD,
STATE_ANNOTATION,
STATE_TAB,
+ STATE_GRID,
STATE_MARKUP_TAG,
STATE_MARKUP_TAG_START,
STATE_MARKUP_TAG_END,
@@ -142,6 +143,12 @@ struct Tag {
bool is_closed;
};
+struct GridShape {
+ int cells;
+ int left;
+ int right;
+};
+
typedef int index_t;
struct ChoContext {
@@ -185,6 +192,7 @@ struct ChoContext {
struct Tag **tags;
struct Config *config;
struct ChoImage **image_assets;
+ struct GridShape grid_shape;
};
void cho_log_enable_info_logs(void);