/*
* Gregorio is a program that translates gabc files to GregorioTeX
* This file implements vowel rule handling (aside from parsing).
*
* Copyright (C) 2015-2019 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;
}