|
@@ -0,0 +1,209 @@
|
|
|
|
|
+/*
|
|
|
|
|
+chunk.c -- working with chunks of Vinora Screenplay.
|
|
|
|
|
+
|
|
|
|
|
+Copyright (c) 2025 Evgeniy "Parthen" Parfenyuk <parthen [at] riseup.net>
|
|
|
|
|
+
|
|
|
|
|
+This program is free software: you can redistribute it and/or modify
|
|
|
|
|
+it under the terms of the GNU General Public License as published by
|
|
|
|
|
+the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
+(at your option) any later version.
|
|
|
|
|
+
|
|
|
|
|
+This program is distributed in the hope that it will be useful,
|
|
|
|
|
+but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
+GNU General Public License for more details.
|
|
|
|
|
+
|
|
|
|
|
+You should have received a copy of the GNU General Public License
|
|
|
|
|
+along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
+*/
|
|
|
|
|
+
|
|
|
|
|
+#include <stdlib.h>
|
|
|
|
|
+#include <string.h>
|
|
|
|
|
+
|
|
|
|
|
+#include "chunks.h"
|
|
|
|
|
+#include "text_utils.h"
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+/* ===============================================================
|
|
|
|
|
+ * check_<chunk>_chunk functions are returning <chunk> if true and
|
|
|
|
|
+ * CHUNK_DIALOG if false. (And CHUNK_EMPTY for invalid lines).
|
|
|
|
|
+ * ===============================================================
|
|
|
|
|
+*/
|
|
|
|
|
+
|
|
|
|
|
+static chunk_t check_character_chunk(const char *s)
|
|
|
|
|
+{
|
|
|
|
|
+ if (!s || s[0] == '\0') return CHUNK_EMPTY;
|
|
|
|
|
+
|
|
|
|
|
+ if (s[0] == '@' && s[strlen(s) - 2] == ' '
|
|
|
|
|
+ && s[strlen(s) - 3] == ' ')
|
|
|
|
|
+ return CHUNK_CHARACTER;
|
|
|
|
|
+
|
|
|
|
|
+ return CHUNK_DIALOG;
|
|
|
|
|
+
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static chunk_t check_header_chunk(const char *s)
|
|
|
|
|
+{
|
|
|
|
|
+ if (!s || s[0] == '\0') return CHUNK_EMPTY;
|
|
|
|
|
+
|
|
|
|
|
+ int hash_count = 0;
|
|
|
|
|
+ for (size_t i = 0; i < strlen(s); i++) {
|
|
|
|
|
+ if (s[i] == '#') {
|
|
|
|
|
+ hash_count++;
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (s[i] == ' ' && hash_count > 0 && hash_count <= 6)
|
|
|
|
|
+ return CHUNK_HEADER;
|
|
|
|
|
+ else
|
|
|
|
|
+ return CHUNK_DIALOG;
|
|
|
|
|
+ }
|
|
|
|
|
+ return CHUNK_DIALOG;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static chunk_t check_choice_chunk(const char *s)
|
|
|
|
|
+{
|
|
|
|
|
+ if (!s || s[0] == '\0') return CHUNK_EMPTY;
|
|
|
|
|
+
|
|
|
|
|
+ if (s[0] != '+' && s[0] != '*' && s[0] != '-')
|
|
|
|
|
+ return CHUNK_DIALOG;
|
|
|
|
|
+
|
|
|
|
|
+ size_t pos = 1;
|
|
|
|
|
+ while (is_whitespace(s[pos])) pos++;
|
|
|
|
|
+ if (s[pos] != '[') return CHUNK_DIALOG;
|
|
|
|
|
+ pos++;
|
|
|
|
|
+
|
|
|
|
|
+ while (s[pos] != ']' && s[pos] != '\0') pos++;
|
|
|
|
|
+ if (s[pos] != ']') return CHUNK_DIALOG;
|
|
|
|
|
+ pos++;
|
|
|
|
|
+
|
|
|
|
|
+ while (is_whitespace(s[pos])) pos++;
|
|
|
|
|
+ if (s[pos] != '(') return CHUNK_DIALOG;
|
|
|
|
|
+ pos++;
|
|
|
|
|
+ int paren_level = 1;
|
|
|
|
|
+ while (s[pos] != '\0') {
|
|
|
|
|
+ if (s[pos] == '(') paren_level++;
|
|
|
|
|
+ else if (s[pos] == ')') { paren_level--; if (paren_level == 0) break;}
|
|
|
|
|
+ pos++;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (paren_level != 0) return CHUNK_DIALOG; // unclosed (
|
|
|
|
|
+
|
|
|
|
|
+ pos++;
|
|
|
|
|
+
|
|
|
|
|
+ return CHUNK_CHOICE;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static chunk_t check_image_chunk(const char *s)
|
|
|
|
|
+{
|
|
|
|
|
+ if (!s || s[0] == '\0') return CHUNK_EMPTY;
|
|
|
|
|
+
|
|
|
|
|
+ if (!s || s[0] != '!' || s[1] == '\0')
|
|
|
|
|
+ return CHUNK_DIALOG;
|
|
|
|
|
+
|
|
|
|
|
+ size_t pos = 1;
|
|
|
|
|
+ while (s[pos] != ']' && s[pos] != '\0' && s[pos+1] != '\0') pos++;
|
|
|
|
|
+ if (s[pos] != ']' || s[pos+1] != '(') return CHUNK_DIALOG;
|
|
|
|
|
+
|
|
|
|
|
+ pos += 2;
|
|
|
|
|
+ while (s[pos] != ')' && s[pos] != '\0') pos++;
|
|
|
|
|
+ if (s[pos] != ')') return CHUNK_DIALOG;
|
|
|
|
|
+
|
|
|
|
|
+ return CHUNK_IMAGE;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static chunk_t check_media_chunk(const char *s)
|
|
|
|
|
+{
|
|
|
|
|
+ if (!s || s[0] == '\0') return CHUNK_EMPTY;
|
|
|
|
|
+
|
|
|
|
|
+ if (!s || s[0] == '\0' || s[1] == '\0')
|
|
|
|
|
+ return CHUNK_DIALOG;
|
|
|
|
|
+
|
|
|
|
|
+ size_t pos = 0;
|
|
|
|
|
+ while (s[pos] != ']' && s[pos] != '\0' && s[pos+1] != '\0') pos++;
|
|
|
|
|
+ if (s[pos] != ']' || s[pos+1] != '(') return CHUNK_DIALOG;
|
|
|
|
|
+
|
|
|
|
|
+ pos += 2;
|
|
|
|
|
+ while (s[pos] != ')' && s[pos] != '\0') pos++;
|
|
|
|
|
+ if (s[pos] != ')') return CHUNK_DIALOG;
|
|
|
|
|
+
|
|
|
|
|
+ return CHUNK_MEDIA;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static chunk_t check_comment_chunk(const char *s)
|
|
|
|
|
+{
|
|
|
|
|
+ if (!s || s[0] == '\0') return CHUNK_EMPTY;
|
|
|
|
|
+
|
|
|
|
|
+ if (!s || s[0] != '>' || s[1] == '\0')
|
|
|
|
|
+ return CHUNK_DIALOG;
|
|
|
|
|
+
|
|
|
|
|
+ return CHUNK_COMMENT;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static chunk_t check_directive_chunk(const char *s)
|
|
|
|
|
+{
|
|
|
|
|
+ if (!s || s[0] == '\0') return CHUNK_EMPTY;
|
|
|
|
|
+
|
|
|
|
|
+ if (s[0] == '{' && s[strlen(s) - 2] == '}' &&
|
|
|
|
|
+ s[1] == '{' && s[strlen(s) - 3] == '}')
|
|
|
|
|
+ return CHUNK_DIRECTIVE;
|
|
|
|
|
+
|
|
|
|
|
+ return CHUNK_DIALOG;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static chunk_t check_literal_chunk(const char *s)
|
|
|
|
|
+{
|
|
|
|
|
+ if (!s || s[0] == '\0') return CHUNK_EMPTY;
|
|
|
|
|
+
|
|
|
|
|
+ if (s[0] == '`' &&
|
|
|
|
|
+ s[1] == '`' &&
|
|
|
|
|
+ s[2] == '`')
|
|
|
|
|
+ return CHUNK_LITERAL;
|
|
|
|
|
+
|
|
|
|
|
+ return CHUNK_DIALOG;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ===============================================================
|
|
|
|
|
+ * End of check_<chunk>_chunk functions
|
|
|
|
|
+ * ===============================================================
|
|
|
|
|
+*/
|
|
|
|
|
+
|
|
|
|
|
+// First line of chunk is enough to identify type
|
|
|
|
|
+chunk_t get_chunk_type(const char *s)
|
|
|
|
|
+{
|
|
|
|
|
+ if (!s || s[0] == '\0') return CHUNK_EMPTY;
|
|
|
|
|
+
|
|
|
|
|
+ switch (s[0]) {
|
|
|
|
|
+ case '@': return check_character_chunk(s); break;
|
|
|
|
|
+ case '#': return check_header_chunk(s); break;
|
|
|
|
|
+
|
|
|
|
|
+ case '+':
|
|
|
|
|
+ case '-':
|
|
|
|
|
+ case '*': return check_choice_chunk(s); break;
|
|
|
|
|
+
|
|
|
|
|
+ case '!': return check_image_chunk(s); break;
|
|
|
|
|
+ case '[': return check_media_chunk(s); break;
|
|
|
|
|
+ case '>': return check_comment_chunk(s); break;
|
|
|
|
|
+ case '{': return check_directive_chunk(s); break;
|
|
|
|
|
+ case '`': return check_literal_chunk(s); break;
|
|
|
|
|
+ default: return CHUNK_DIALOG; break;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+char *chunk_type_to_name(const chunk_t t)
|
|
|
|
|
+{
|
|
|
|
|
+ switch (t) {
|
|
|
|
|
+ case CHUNK_DIALOG: return "DIALOG";
|
|
|
|
|
+ case CHUNK_CHARACTER: return "CHARACTER";
|
|
|
|
|
+ case CHUNK_HEADER: return "HEADER";
|
|
|
|
|
+ case CHUNK_CHOICE: return "CHOICE";
|
|
|
|
|
+ case CHUNK_IMAGE: return "IMAGE";
|
|
|
|
|
+ case CHUNK_MEDIA: return "MEDIA";
|
|
|
|
|
+ case CHUNK_COMMENT: return "COMMENT";
|
|
|
|
|
+ case CHUNK_DIRECTIVE: return "DIRECTIVE";
|
|
|
|
|
+ case CHUNK_LITERAL: return "LITERAL";
|
|
|
|
|
+ case CHUNK_EMPTY: return "EMPTY";
|
|
|
|
|
+ default: return "UNKNOWN";
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|