/* * Gregorio is a program that translates gabc files to GregorioTeX * This file implements vowel rule handling (aside from parsing). * * Copyright (C) 2015-2021 The Gregorio Project (see CONTRIBUTORS.md) * * This file is part of Gregorio. * * 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 . */ #include "config.h" #include #include #include #include #include #include "bool.h" #include "vowel.h" #include "unicode.h" #include "messages.h" #include "support.h" typedef struct character_set { grewchar *table; struct character_set **next; grewchar mask; unsigned int bins; unsigned int size; bool is_final; } character_set; static character_set *character_set_new(const bool alloc_next) { character_set *set = (character_set *)gregorio_calloc(1, sizeof(character_set)); set->mask = 0x0f; set->bins = 16; set->size = 0; set->table = (grewchar *)gregorio_calloc(set->bins, sizeof(grewchar)); if (alloc_next) { set->next = (character_set **)gregorio_calloc(set->bins, sizeof(character_set *)); } return set; } static void character_set_free(character_set *const set); static __inline void character_set_next_elements_free(character_set *const set) { unsigned int i; assert(set && set->next); for (i = 0; i < set->bins; ++i) { if (set->next[i]) { character_set_free(set->next[i]); } } } static void character_set_free(character_set *const set) { if (set) { if (set->next) { character_set_next_elements_free(set); free(set->next); } free(set->table); free(set); } } static bool character_set_contains(character_set *const set, const grewchar character, character_set **const next) { unsigned int index; assert(set); if (next) { *next = NULL; } for (index = ((unsigned long)character) & set->mask; set->table[index]; index = (index + 1) & set->mask) { if (set->table[index] == character) { if (next && set->next) { *next = set->next[index]; } return true; } } return false; } static __inline void character_set_put(character_set *const set, const grewchar character, character_set *next) { unsigned int index; assert(set); index = ((unsigned long)character) & set->mask; while (set->table[index]) { index = (index + 1) & set->mask; } set->table[index] = character; if (set->next) { set->next[index] = next; } } static __inline void character_set_grow(character_set *const set) { static grewchar *old_table; static character_set **old_next; unsigned int old_bins, i; assert(set); if (set->bins >= 0x4000L) { /* this should not realistically be reached */ /* LCOV_EXCL_START */ gregorio_message(_("character set too large"), "character_set_grow", VERBOSITY_FATAL, 0); return; /* LCOV_EXCL_STOP */ } old_table = set->table; old_next = set->next; old_bins = set->bins; set->bins <<= 1; set->mask = (set->mask << 1) | 0x01; set->table = gregorio_calloc(set->bins, sizeof(grewchar)); if (old_next) { set->next = gregorio_calloc(set->bins, sizeof(character_set *)); } for (i = 0; i < old_bins; ++i) { if (old_table[i]) { character_set_put(set, old_table[i], old_next ? old_next[i] : NULL); } else { assert(!old_next || !old_next[i]); } } free(old_table); if (old_next) { free(old_next); } } static character_set *character_set_add(character_set *const set, const grewchar character) { character_set *next = NULL; if (!character_set_contains(set, character, &next)) { if (((++(set->size) * 10) / set->bins) >= 7) { character_set_grow(set); } if (set->next) { next = character_set_new(true); } character_set_put(set, character, next); } return next; } static __inline void character_set_clear(character_set *const set) { if (set) { memset(set->table, 0, set->bins * sizeof(grewchar)); if (set->next) { character_set_next_elements_free(set); memset(set->next, 0, set->bins * sizeof(character_set *)); } set->size = 0; } } static character_set *vowel_table = NULL; static character_set *prefix_table = NULL; static character_set *suffix_table = NULL; static character_set *secondary_table = NULL; static grewchar *prefix_buffer = NULL; static size_t prefix_buffer_size; static size_t prefix_buffer_mask; extern FILE *gregorio_vowel_rulefile_in; static void prefix_buffer_grow(size_t required_size) { while (required_size > prefix_buffer_size) { prefix_buffer_size <<= 1; prefix_buffer_mask = (prefix_buffer_mask << 1) | 0x01; } prefix_buffer = gregorio_realloc(prefix_buffer, prefix_buffer_size * sizeof(grewchar)); } void gregorio_vowel_tables_init(void) { if (vowel_table) { character_set_clear(vowel_table); character_set_clear(prefix_table); character_set_clear(suffix_table); character_set_clear(secondary_table); } else { vowel_table = character_set_new(false); prefix_table = character_set_new(true); suffix_table = character_set_new(true); secondary_table = character_set_new(true); prefix_buffer_size = 16; prefix_buffer_mask = 0x0f; prefix_buffer = (grewchar *)gregorio_malloc( prefix_buffer_size * sizeof(grewchar)); } } void gregorio_vowel_tables_load(const char *const filename, char **const language, rulefile_parse_status *status) { gregorio_check_file_access(read, filename, WARNING, return); gregorio_vowel_rulefile_in = fopen(filename, "r"); if (gregorio_vowel_rulefile_in) { gregorio_vowel_rulefile_parse(filename, language, status); fclose(gregorio_vowel_rulefile_in); gregorio_vowel_rulefile_in = NULL; } else { /* tests cannot be expected to simulate the system error that would * cause gregorio_vowel_rulefile_in to be NULL */ /* LCOV_EXCL_START */ gregorio_messagef("gregorio_vowel_tables_load", VERBOSITY_WARNING, 0, _("unable to open %s: %s"), filename, strerror(errno)); /* LCOV_EXCL_STOP */ } } void gregorio_vowel_tables_free(void) { if (vowel_table) { character_set_free(vowel_table); } if (prefix_table) { character_set_free(prefix_table); } if (suffix_table) { character_set_free(suffix_table); } if (secondary_table) { character_set_free(secondary_table); } if (prefix_buffer) { free(prefix_buffer); } } void gregorio_vowel_table_add(const char *vowels) { if (vowels) { grewchar *str = gregorio_build_grewchar_string_from_buf(vowels), *p; for (p = str; *p; ++p) { character_set_add(vowel_table, *p); } free(str); } } void gregorio_prefix_table_add(const char *prefix) { character_set *set = prefix_table; grewchar *str, *p; /* store prefixes backwards */ if (prefix && *prefix) { str = gregorio_build_grewchar_string_from_buf(prefix); p = str; while (*(++p)) ; while (p > str) { set = character_set_add(set, *(--p)); } set->is_final = true; prefix_buffer_grow(gregorio_wcstrlen(str)); free(str); } } void gregorio_suffix_table_add(const char *suffix) { character_set *set = suffix_table; grewchar *str, *p; if (suffix && *suffix) { str = gregorio_build_grewchar_string_from_buf(suffix); p = str; while (*p) { set = character_set_add(set, *(p++)); } set->is_final = true; free(str); } } void gregorio_secondary_table_add(const char *secondary) { character_set *set = secondary_table; grewchar *str, *p; if (secondary && *secondary) { str = gregorio_build_grewchar_string_from_buf(secondary); p = str; while (*p) { set = character_set_add(set, *(p++)); } set->is_final = true; free(str); } } typedef enum { VWL_BEFORE = 0, VWL_WITHIN, VWL_SUFFIX } vowel_group_state; static __inline bool is_in_prefix(size_t bufpos) { character_set *previous = prefix_table, *prefix; while (character_set_contains(previous, prefix_buffer[bufpos], &prefix)) { bufpos = (bufpos + prefix_buffer_size - 1) & prefix_buffer_mask; previous = prefix; } return previous->is_final; } bool gregorio_find_vowel_group(const grewchar *const string, int *const start, int *const end) { size_t bufpos = 0; vowel_group_state state = VWL_BEFORE; character_set *cset = NULL; const grewchar *subject; int i; /* stick the 0 in here to avoid overruns from is_in_prefix */ prefix_buffer[0] = 0; for (i = 0, subject = string; true; ++i, ++subject) { switch (state) { case VWL_BEFORE: bufpos = (bufpos + 1) & prefix_buffer_mask; prefix_buffer[bufpos] = *subject; if (character_set_contains(vowel_table, *subject, NULL)) { /* we found a vowel */ if ((!character_set_contains(vowel_table, *(subject + 1), NULL) && *(subject + 1) != GREVOWEL_ELISION_MARK) || !is_in_prefix(bufpos)) { /* no vowel/elision after, or not in prefix, so this is the * start */ *start = i; state = VWL_WITHIN; } } break; case VWL_WITHIN: if (!character_set_contains(vowel_table, *subject, NULL)) { /* not a vowel; is it a suffix? */ *end = i; if (character_set_contains(suffix_table, *subject, &cset)) { /* it's the start of a suffix */ state = VWL_SUFFIX; } else { /* neither a vowel nor a suffix, so this is the end */ return true; } } break; case VWL_SUFFIX: if (cset->is_final) { /* remember the last final position */ *end = i; } if (!character_set_contains(cset, *subject, &cset)) { /* no longer in valid suffix */ return true; } break; default: /* not reachable unless there's a programming error */ /* LCOV_EXCL_START */ assert(false); break; /* LCOV_EXCL_STOP */ } if (!*subject) { break; } } /* no vowel found; look for a secondary */ *end = -1; for (*start = 0; *(subject = string + *start); ++ *start) { for (cset = secondary_table, i = *start; character_set_contains(cset, *subject, &cset); ++i, ++subject) { if (cset->is_final) { /* remember the last final position */ *end = i + 1; } } if (*end >= 0) { return true; } } /* no center found */ *start = -1; return false; }