524 lines
9.6 KiB
C
524 lines
9.6 KiB
C
/*
|
|
** SOUNDS.C
|
|
**
|
|
** Sound Change Applier
|
|
**
|
|
** Copyright (C) 2000 by Mark Rosenfelder.
|
|
** This program may be freely used and modified for non-commercial purposes.
|
|
** See http://www.zompist.com/sounds.htm for documentation.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
|
|
#define TRUE 1
|
|
#define FALSE 0
|
|
|
|
static int printRules = 0;
|
|
static int bracketOut = 0;
|
|
static int printSourc = 1;
|
|
static int toScreen = 1;
|
|
|
|
#define MAXRULE 200
|
|
#define MAXCAT 50
|
|
|
|
|
|
static int nRule = 0;
|
|
static char *Rule[MAXRULE];
|
|
|
|
static int nCat = 0;
|
|
static char *Cat[MAXCAT];
|
|
|
|
/*
|
|
** ReadRules
|
|
**
|
|
** Read in the rules file *.sc for a given project.
|
|
**
|
|
** There are two types of rules: sound changes and category definitions.
|
|
** The former are stored in Rule[], the latter in Cat[].
|
|
**
|
|
** The format of these rules is given under Transform().
|
|
*/
|
|
int ReadRules( char *filestart )
|
|
{
|
|
char filename[84];
|
|
char buffer[129];
|
|
char *s;
|
|
int n;
|
|
FILE *f;
|
|
|
|
nRule = 0;
|
|
nCat = 0;
|
|
|
|
/* Open the file */
|
|
|
|
sprintf( filename, "%s.sc", filestart );
|
|
|
|
f = fopen( filename, "r" );
|
|
if (!f)
|
|
{
|
|
printf( "File %s could not be read in.\n\n", filename );
|
|
return(FALSE);
|
|
}
|
|
|
|
while (fgets( buffer, 129, f))
|
|
{
|
|
if (strlen(buffer))
|
|
buffer[strlen(buffer)-1] = '\0';
|
|
|
|
s = malloc( strlen(buffer) + 1);
|
|
if (s)
|
|
strcpy( s, buffer );
|
|
|
|
if (buffer[0] != '*')
|
|
{
|
|
if (strchr( buffer, '/' ))
|
|
Rule[nRule++] = s;
|
|
else if (strchr( buffer, '='))
|
|
Cat[ nCat++] = s;
|
|
}
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
if (nCat)
|
|
{
|
|
printf( "%i categories found\n", nCat );
|
|
|
|
#ifdef PRINT_RULES
|
|
for (n = 0; n < nCat; n++)
|
|
printf( "%s\n", Cat[n] );
|
|
|
|
printf( "\n" );
|
|
#endif
|
|
}
|
|
else
|
|
printf( "No rules were found.\n\n" );
|
|
|
|
if (nRule)
|
|
{
|
|
printf( "%i rules found\n", nRule );
|
|
|
|
#ifdef PRINT_RULES
|
|
for (n = 0; n < nRule; n++)
|
|
printf( "%s\n", Rule[n] );
|
|
|
|
printf( "\n" );
|
|
#endif
|
|
}
|
|
else
|
|
printf( "No rules were found.\n\n" );
|
|
|
|
return( nRule );
|
|
|
|
} /*ReadRules*/
|
|
|
|
|
|
/*
|
|
** Divide
|
|
**
|
|
** Divide a rule into source and target phoneme(s) and environment.
|
|
** That is, for a rule s1/s2/env
|
|
** create the three null-terminated strings s1, s2, and env.
|
|
**
|
|
** If this cannot be done, return FALSE.
|
|
*/
|
|
int Divide( char *Rule, char **s1, char **s2, char **env )
|
|
{
|
|
size_t i;
|
|
static char s1_str[20];
|
|
static char s2_str[20];
|
|
static char ev_str[50];
|
|
|
|
i = strcspn( Rule, "/" );
|
|
if (i == 0 || i > 19)
|
|
return(FALSE);
|
|
|
|
strncpy( s1_str, Rule, i );
|
|
s1_str[i] = '\0';
|
|
Rule += i + 1;
|
|
|
|
i = strcspn( Rule, "/" );
|
|
if (i > 19)
|
|
return(FALSE);
|
|
|
|
if (i)
|
|
strncpy( s2_str, Rule, i );
|
|
s2_str[i] = '\0';
|
|
Rule += i + 1;
|
|
|
|
strcpy( ev_str, Rule );
|
|
|
|
*s1 = s1_str;
|
|
*s2 = s2_str;
|
|
*env = ev_str;
|
|
|
|
return(TRUE);
|
|
|
|
} /*Divide*/
|
|
|
|
|
|
/*
|
|
** TryCat
|
|
**
|
|
** See if a particular phoneme sequence is part of any category.
|
|
** (We try all the categories.)
|
|
**
|
|
** For instance, if we have 'a' in the source word and 'V' in the
|
|
** structural description, and a category V=aeiou, TryCat returns TRUE,
|
|
** and sets *n to the number of characters to skip.
|
|
**
|
|
** If we had 'b' instead, TryCat would return FALSE instead.
|
|
**
|
|
** If no category with the given identification (env) can be found,
|
|
** we return TRUE (continue looking), but set *n to 0.
|
|
**
|
|
** Warning: For now, we don't have a way to handle digraphs.
|
|
**
|
|
** We also return TRUE if
|
|
*/
|
|
int TryCat( char *env, char *word, int *n, int *catLoc )
|
|
{
|
|
int c;
|
|
char *catdef;
|
|
|
|
if (*word == '\0')
|
|
return(FALSE);
|
|
|
|
for (c = 0; c < nCat; c++)
|
|
{
|
|
if (*env == *Cat[c])
|
|
{
|
|
catdef = strchr( Cat[c], '=' );
|
|
|
|
if (strchr( catdef + 1, word[0] ))
|
|
{
|
|
*n = 1;
|
|
*catLoc = strchr( Cat[c], word[0] ) - Cat[c];
|
|
return(TRUE);
|
|
}
|
|
else
|
|
return(FALSE);
|
|
}
|
|
}
|
|
|
|
*n = 0;
|
|
return(TRUE);
|
|
|
|
} /*TryCat*/
|
|
|
|
/*
|
|
** TryRule
|
|
**
|
|
** See if a rule s1->s2/env applies at position i in the given word.
|
|
**
|
|
** If it does, we pass back the index where s1 was found in the
|
|
** word, as well as s1 and s2, and return TRUE.
|
|
**
|
|
** Otherwise, we return FALSE, and pass garbage in the output variables.
|
|
*/
|
|
int TryRule( char *word, int i, char *Rule, int *n, char **s1, char **s2, char *varRep )
|
|
{
|
|
int j, m, cont = 0;
|
|
int catLoc;
|
|
char *env;
|
|
int optional = FALSE;
|
|
*varRep = '\0';
|
|
|
|
if (!Divide( Rule, s1, s2, &env ) || !strchr( env, '_' ))
|
|
return(FALSE);
|
|
|
|
for (j = 0, cont = TRUE; cont && j < strlen(env); j++)
|
|
{
|
|
switch( env[j] )
|
|
{
|
|
case '(':
|
|
optional = TRUE;
|
|
break;
|
|
|
|
case ')':
|
|
optional = FALSE;
|
|
break;
|
|
|
|
case '#':
|
|
cont = j ? (i == strlen(word)) : (i == 0);
|
|
break;
|
|
|
|
case '_':
|
|
cont = !strncmp( &word[i], *s1, strlen(*s1) );
|
|
if (cont)
|
|
{
|
|
*n = i;
|
|
i += strlen(*s1);
|
|
}
|
|
else
|
|
{
|
|
cont = TryCat( *s1, &word[i], &m, &catLoc );
|
|
if (cont && m)
|
|
{
|
|
int c;
|
|
*n = i;
|
|
i += m;
|
|
|
|
for (c = 0; c < nCat; c++)
|
|
if ((*s2)[0] == Cat[c][0] && catLoc < strlen(Cat[c]))
|
|
*varRep = Cat[c][catLoc];
|
|
}
|
|
else if (cont)
|
|
cont = FALSE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
cont = TryCat( &env[j], &word[i], &m, &catLoc );
|
|
|
|
if (cont && !m)
|
|
{
|
|
/* no category applied */
|
|
|
|
cont = i < strlen(word) && word[i] == env[j];
|
|
|
|
m = 1;
|
|
}
|
|
if (cont)
|
|
i += m;
|
|
|
|
if (!cont && optional)
|
|
cont = TRUE;
|
|
}
|
|
}
|
|
|
|
if (cont && printRules)
|
|
printf( " %s->%s /%s applies to %s at %i\n",
|
|
*s1, *s2, env, word, *n );
|
|
|
|
return(cont);
|
|
|
|
} /*TryRule*/
|
|
|
|
/*
|
|
** Transform
|
|
**
|
|
** Apply the rules to a single word and return the result.
|
|
**
|
|
** The rules are stated in the form string1/string2/environment, e.g.
|
|
** f/h/#_V
|
|
** which states that f changes to h at the beginning of a word before a
|
|
** vowel.
|
|
*/
|
|
char *Transform( char *input )
|
|
{
|
|
char inword[80];
|
|
static char outword[80];
|
|
|
|
char instr[10];
|
|
char *s1, *s2;
|
|
int i;
|
|
int r;
|
|
int n;
|
|
|
|
strcpy( inword, input );
|
|
|
|
/* Try to apply each rule in turn */
|
|
|
|
for (r = 0; r < nRule; r++)
|
|
{
|
|
/* Initialize output of this rule to null */
|
|
|
|
memset( outword, 0, 80 );
|
|
|
|
/* Check each position of the input word in turn */
|
|
|
|
i = 0;
|
|
while (i < strlen(inword))
|
|
{
|
|
char varRep = 0;
|
|
|
|
if (TryRule( inword, i, Rule[r], &n, &s1, &s2, &varRep ))
|
|
{
|
|
/* Rule applies at inword[n] */
|
|
|
|
if (n)
|
|
strncat( outword, &inword[i], n - i );
|
|
|
|
if (varRep)
|
|
outword[strlen(outword)] = varRep;
|
|
else if (strlen(s2))
|
|
strcat( outword, s2 );
|
|
|
|
i = n + strlen(s1);
|
|
}
|
|
else
|
|
{
|
|
/* Rule doesn't apply at this location */
|
|
|
|
outword[strlen(outword)] = inword[i++];
|
|
}
|
|
}
|
|
|
|
/* Output of one rule is input to next one */
|
|
|
|
strcpy( inword, outword );
|
|
}
|
|
|
|
/* Return the output of the last rule */
|
|
|
|
return(outword);
|
|
|
|
} /*Transform*/
|
|
|
|
/*
|
|
** DoWords
|
|
**
|
|
** Read in each word in turn from the input file,
|
|
** transform it according to the rules,
|
|
** and output it to the output file.
|
|
**
|
|
** This algorithm ensures that word files of any size can be processed.
|
|
*/
|
|
void DoWords( char *lexname, char *outname )
|
|
{
|
|
char filename[84];
|
|
char inword[84];
|
|
int n = 0;
|
|
FILE *f, *g;
|
|
char *outword;
|
|
|
|
sprintf( filename, "%s.lex", lexname );
|
|
|
|
f = fopen( filename, "r" );
|
|
if (!f)
|
|
{
|
|
printf( "File %s could not be read in.\n\n", filename );
|
|
return;
|
|
}
|
|
|
|
sprintf( filename, "%s.out", outname );
|
|
|
|
g = fopen( filename, "w" );
|
|
if (!g)
|
|
{
|
|
printf( "File %s could not be created.\n\n", filename );
|
|
fclose(f);
|
|
return;
|
|
}
|
|
|
|
while (fgets( inword, 129, f))
|
|
{
|
|
n++;
|
|
if (strlen(inword))
|
|
inword[strlen(inword) - 1] = '\0';
|
|
|
|
outword = Transform(inword);
|
|
|
|
if (!printSourc)
|
|
{
|
|
if (toScreen)
|
|
printf( "%s\n", outword );
|
|
fprintf( g, "%s\n", outword );
|
|
}
|
|
else if (bracketOut)
|
|
{
|
|
if (toScreen)
|
|
printf( "%s \t[%s]\n", outword, inword );
|
|
fprintf( g, "%s \t[%s]\n", outword, inword );
|
|
}
|
|
else
|
|
{
|
|
if (toScreen)
|
|
printf( "%s --> %s\n", inword, outword );
|
|
fprintf( g, "%s --> %s\n", inword, outword );
|
|
}
|
|
}
|
|
|
|
fclose(f);
|
|
fclose(g);
|
|
|
|
printf( "%i word%s processed.\n", n, n == 1 ? "" : "s" );
|
|
|
|
} /*DoWords*/
|
|
|
|
/*
|
|
** MAIN ROUTINE
|
|
**
|
|
** Ask for name of project
|
|
** Read in rules and input words
|
|
** Apply transformations
|
|
** Output words
|
|
**
|
|
*/
|
|
main( int argc, char **argv )
|
|
{
|
|
int once = FALSE;
|
|
char lexicon[65] = "\0";
|
|
char rules[65] = "\0";
|
|
|
|
/* Read command line arguments */
|
|
int i;
|
|
for (i = 1; i < argc; i++)
|
|
{
|
|
if (argv[i][0] == '-' && strlen(argv[i]) > 1)
|
|
{
|
|
switch (argv[i][1])
|
|
{
|
|
case 'p': case 'P': printRules = 1; break;
|
|
case 'b': case 'B': bracketOut = 1; break;
|
|
case 'l': case 'L': printSourc = 0; break;
|
|
case 'f': case 'F': toScreen = 0; break;
|
|
}
|
|
}
|
|
else if (!lexicon[0])
|
|
strcpy( lexicon, argv[i] );
|
|
else
|
|
strcpy( rules, argv[i] );
|
|
}
|
|
|
|
once = lexicon[0] && rules[0];
|
|
|
|
printf( "\nSOUND CHANGE APPLIER\n(C) 1992,2000 by Mark Rosenfelder\nFor more information see www.zompist.com\n\n" );
|
|
|
|
if (once)
|
|
{
|
|
printf( "Applying %s.sc to %s.lex\n\n", lexicon, rules );
|
|
|
|
if (ReadRules( rules ))
|
|
DoWords( lexicon, rules );
|
|
}
|
|
else
|
|
{
|
|
int done = FALSE;
|
|
while (!done)
|
|
{
|
|
printf( "\nEnter the name of a LEXICON.\n\n" );
|
|
printf( "For example, enter latin to specify latin.lex.\nEnter q to quit the program.\n-->" );
|
|
|
|
fgets( lexicon, 65, stdin );
|
|
|
|
if (strlen(lexicon))
|
|
lexicon[strlen(lexicon) - 1] = '\0';
|
|
|
|
if (!strcmp( lexicon, "q" ))
|
|
done = TRUE;
|
|
else
|
|
{
|
|
printf( "Enter the name of a RULES FILE.\n\n" );
|
|
printf( "For example, enter french to specify french.sc.\n" );
|
|
printf( "The output words would be stored in french.out.\n-->" );
|
|
|
|
fgets( rules, 65, stdin );
|
|
|
|
if (strlen(rules))
|
|
rules[strlen(rules) - 1] = '\0';
|
|
|
|
if (ReadRules( rules ))
|
|
DoWords( lexicon, rules );
|
|
}
|
|
}
|
|
}
|
|
|
|
printf( "\nThank you for using the SOUND CHANGE APPLIER!\n" );
|
|
|
|
} /*main*/ |