Golf a Sound Change Applier
(Snappier title suggestions appreciated)
code-golf interpreter parsing
Background
The pronunciation of human languages changes over time. Often, sound changes follow discernable rules, with the same change applied consistently across many words in a language. For example, a lot of words that started with f in Latin now start with h in Spanish: facere -> hacer, fervere -> hervir, fīcus -> higo, and fīlius -> hijo.
Mark Rosenfelder, a well-known name in conlang circles, wrote a program called Sound Change Applier to help simulate this process. Actually, the SCA constitutes a small pattern-matching and substitution language. For this challenge, we're going to implement a pared-down version.
The language
An SCA program transforms a list of words according to a collection of sound change rules. It consists of two sections: category definitions and substitutions.
Category definitions are lines of the form V=aeiou
, where V
is the category name (must be a single character) and aeiou
are the category entries. Categories define groups of similar speech sounds (we'll use the term "letters," though strictly speaking that's a bit misleading); for instance, the above example is the category of vowels.
Substitutions are where the magic happens. They take the form target/replacement/environment
, where the constituent parts are as follows:
target
and replacement
are strings containing any number of letters and 0 or 1 category names, in any order. They represent the string to be replaced and the string to replace it with, respectively.
environment
is a string containing exactly 1 underscore (_
), representing the substitution, and any number of letters and category names, representing the required context for the substitution to take place. It can also contain the #
character, representing the beginning or end of a word.
Some examples, assuming the V
category from earlier:
ii/i/_ Change ii to i anywhere in a word
i/j/_V Change i to j when followed by a vowel
u/o/_# Change u to o at the end of a word
p//V_t Delete p if preceded by a vowel and followed by t
Categories in substitutions
When category names are used in the environment, the environment matches if any member of the category is in the appropriate place. For example, the last rule above will delete the p
from apt
, opt
, inept
, or scripting
, but not from thbpt
.
The same is true when categories are used in the target but not the replacement. For example, the rule V/e/Vr_#
will delete any vowel if it matches the environment: opera
-> oper
, sombrero
-> sombrer
, calamari
-> calamar
(but not cobra
-> cobr
because the environment vowel isn't matched).
However, if categories are used in both the target and replacement, the replacement letter must come from the same position in its category as the matched target letter does in its category.
For example, suppose we have these category definitions: S=ptc
, Z=bdg
. (These are unvoiced and voiced stops, respectively.) Then the substitution S/Z/V_V
(changed unvoiced stops to voiced between vowels) represents three possible substitutions: p
-> b
, t
-> d
, and c
-> g
.
Execution
An SCA program accepts as input a list of words to be transformed. It goes down the list of substitutions in order, applying each one to all words in the word list. Each substitution is applied repeatedly until the environment and/or target no longer matches. Then the next substitution is applied. When all substitutions have completed, the program outputs the transformed list of words.
Miscellaneous rules
If a category is used in the replacement, a category must also be used in the target.
The replacement may be empty, but the target may not be empty.
Category names, letters, and words in the word list will never use the characters =/#_
or space.
The challenge
Write a program or function that takes an SCA program and a list of words and outputs/returns the result of running that program on those words.
You may assume that the SCA program is syntactically valid and that the words do not contain any forbidden characters. If these assumptions are broken, your code may do anything (handle it gracefully, crash, output gibberish).
I/O format
Input format is flexible. You may take the program as a multiline string or a list of strings. The category definitions and substitutions may be in the same string/list or two different strings/lists (or contained in a two-item list). The input words may be in a list or a space- or newline-delimited string. Similarly, output may be a list or a space- or newline-delimited string.
Any of the default I/O methods are acceptable. You may use different input methods for category definitions, substitutions, and input words if it makes the task easier in your language.
Linguists use many letter forms that are not present in ASCII. Therefore, your program must be able to accept Unicode characters up through U+1FFF (at least) in both the rules and the word list.
Example
Adapted from Mark Rosenfelder's site, here is a simplistic Latin-to-Portuguese converter.
Categories:
V=aeiou
L=āēīōū
C=ptcqbdgmnlrhs
F=ie
B=ou
S=ptc
Z=bdg
Substitutions:
m//_#
i/j/_V
L/V/_
e//Vr_#
v//V_V
u/o/_#
gn/nh/_
S/Z/V_V
c/i/F_t
c/u/B_t
p//V_t
ii/i/_
e//C_rV
lj/lh/_
Given the following input list:
lēctōrem
doctōrem
focum
jocum
districtum
cīvitātem
adoptāre
opera
secundum
fīliam
pontem
the output list should be as follows:
leitor
doutor
fogo
jogo
distrito
cidade
adotar
obra
segundo
filha
ponte
This is code-golf; the shortest code wins.