phonology 0.0.1 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/phonology/features.rb +137 -114
- data/lib/phonology/{sounds.rb → inventory.rb} +29 -9
- data/lib/phonology/orthography.rb +117 -0
- data/lib/phonology/rule.rb +103 -0
- data/lib/phonology/sound.rb +142 -49
- data/lib/phonology/sound_sequence.rb +45 -0
- data/lib/phonology/version.rb +1 -1
- data/lib/phonology.rb +7 -4
- data/test/affricate_test.rb +21 -0
- data/test/features_test.rb +10 -2
- data/test/{sounds_test.rb → inventory_test.rb} +14 -1
- data/test/phonology_test.rb +4 -4
- data/test/rule_test.rb +40 -0
- data/test/sound_test.rb +21 -5
- metadata +10 -5
data/lib/phonology/features.rb
CHANGED
@@ -8,8 +8,9 @@ module Phonology
|
|
8
8
|
|
9
9
|
# Pulmonic consonants
|
10
10
|
PULMONIC = [:nasal, :plosive, :fricative, :approximant, :trill, :flap,
|
11
|
-
:
|
12
|
-
:
|
11
|
+
:lateral_fricative, :lateral_approximant, :lateral_flap, :bilabial,
|
12
|
+
:labiodental, :dental, :alveolar, :postalveolar, :retroflex, :palatal,
|
13
|
+
:velar, :uvular, :pharyngeal, :epiglottal, :glottal]
|
13
14
|
|
14
15
|
# Non-pulmonic consonants
|
15
16
|
NON_PULMONIC = [:click, :implosive, :ejective]
|
@@ -25,7 +26,8 @@ module Phonology
|
|
25
26
|
ALL = ([:voiced] + PULMONIC + NON_PULMONIC + VOCALIC).to_set
|
26
27
|
|
27
28
|
# Manner of articulation features
|
28
|
-
MANNER = [:nasal, :plosive, :fricative, :approximant, :trill, :flap, :
|
29
|
+
MANNER = [:nasal, :plosive, :fricative, :approximant, :trill, :flap, :lateral_fricative,
|
30
|
+
:lateral_approximant, :lateral_flap] + NON_PULMONIC
|
29
31
|
|
30
32
|
# Place of articulation features
|
31
33
|
PLACE = CONSONANTAL.to_a - MANNER
|
@@ -42,122 +44,135 @@ module Phonology
|
|
42
44
|
:coronal => [:dental, :alveolar, :postalveolar, :retroflex].to_set,
|
43
45
|
:dorsal => [:palatal, :velar, :uvular].to_set,
|
44
46
|
:radical => [:pharyngeal, :epiglottal].to_set,
|
45
|
-
:
|
46
|
-
:
|
47
|
-
:
|
47
|
+
:lateral => [:lateral_fricative, :lateral_approximant, :lateral_flap].to_set,
|
48
|
+
:liquid => [:flap, :lateral_fricative, :lateral_approximant, :lateral_flap].to_set,
|
49
|
+
:vocalic => (VOCALIC + [:rounded]).to_set,
|
50
|
+
:vocoid => (VOCALIC + [:approximant]).to_set,
|
51
|
+
:non_pulmonic => NON_PULMONIC.to_set,
|
52
|
+
:pulmonic => PULMONIC.to_set,
|
48
53
|
:consonantal => CONSONANTAL
|
49
54
|
}
|
50
55
|
|
56
|
+
# IPA diacritics for some sound modifiers such as devoicing, affricitization, etc.
|
57
|
+
DIACRITICS = {
|
58
|
+
:affricate => [0x0361]
|
59
|
+
}
|
60
|
+
|
51
61
|
# Sets of distinctive features mapped to an IPA symbol
|
52
62
|
SETS = {
|
53
|
-
[:voiced, :bilabial, :nasal].to_set
|
54
|
-
[:voiced, :labiodental, :nasal].to_set
|
55
|
-
[:voiced, :alveolar, :nasal].to_set
|
56
|
-
[:voiced, :retroflex, :nasal].to_set
|
57
|
-
[:voiced, :palatal, :nasal].to_set
|
58
|
-
[:voiced, :velar, :nasal].to_set
|
59
|
-
[:voiced, :uvular, :nasal].to_set
|
60
|
-
[:bilabial, :plosive].to_set
|
61
|
-
[:voiced, :bilabial, :plosive].to_set
|
62
|
-
[:labiodental, :plosive].to_set
|
63
|
-
[:voiced, :labiodental, :plosive].to_set
|
64
|
-
[:alveolar, :plosive].to_set
|
65
|
-
[:voiced, :alveolar, :plosive].to_set
|
66
|
-
[:retroflex, :plosive].to_set
|
67
|
-
[:voiced, :retroflex, :plosive].to_set
|
68
|
-
[:palatal, :plosive].to_set
|
69
|
-
[:voiced, :palatal, :plosive].to_set
|
70
|
-
[:velar, :plosive].to_set
|
71
|
-
[:voiced, :velar, :plosive].to_set
|
72
|
-
[:uvular, :plosive].to_set
|
73
|
-
[:voiced, :uvular, :plosive].to_set
|
74
|
-
[:voiced, :epiglottal, :plosive].to_set
|
75
|
-
[:voiced, :glottal, :plosive].to_set
|
76
|
-
[:bilabial, :fricative].to_set
|
77
|
-
[:voiced, :bilabial, :fricative].to_set
|
78
|
-
[:labiodental, :fricative].to_set
|
79
|
-
[:voiced, :labiodental, :fricative].to_set
|
80
|
-
[:dental, :fricative].to_set
|
81
|
-
[:voiced, :dental, :fricative].to_set
|
82
|
-
[:alveolar, :fricative].to_set
|
83
|
-
[:voiced, :alveolar, :fricative].to_set
|
84
|
-
[:postalveolar, :fricative].to_set
|
85
|
-
[:voiced, :postalveolar, :fricative].to_set
|
86
|
-
[:retroflex, :fricative].to_set
|
87
|
-
[:voiced, :retroflex, :fricative].to_set
|
88
|
-
[:palatal, :fricative].to_set
|
89
|
-
[:voiced, :palatal, :fricative].to_set
|
90
|
-
[:velar, :fricative].to_set
|
91
|
-
[:voiced, :velar, :fricative].to_set
|
92
|
-
[:uvular, :fricative].to_set
|
93
|
-
[:voiced, :uvular, :fricative, :approximant].to_set
|
94
|
-
[:pharyngeal, :fricative].to_set
|
95
|
-
[:voiced, :pharyngeal, :fricative, :approximant].to_set
|
96
|
-
[:epiglottal, :fricative].to_set
|
97
|
-
[:voiced, :epiglottal, :fricative, :approximant].to_set
|
98
|
-
[:glottal, :fricative, :approximant].to_set
|
99
|
-
[:voiced, :glottal, :fricative, :approximant].to_set
|
100
|
-
[:voiced, :bilabial, :approximant].to_set
|
101
|
-
[:voiced, :labiodental, :approximant].to_set
|
102
|
-
[:voiced, :alveolar, :approximant].to_set
|
103
|
-
[:voiced, :retroflex, :approximant].to_set
|
104
|
-
[:voiced, :palatal, :approximant].to_set
|
105
|
-
[:voiced, :velar, :approximant].to_set
|
106
|
-
[:voiced, :bilabial, :trill].to_set
|
107
|
-
[:voiced, :alveolar, :trill].to_set
|
108
|
-
[:voiced, :uvular, :trill].to_set
|
109
|
-
[:voiced, :epiglottal, :trill].to_set
|
110
|
-
[:voiced, :bilabial, :flap].to_set
|
111
|
-
[:voiced, :labiodental, :flap].to_set
|
112
|
-
[:voiced, :alveolar, :flap].to_set
|
113
|
-
[:voiced, :retroflex, :flap].to_set
|
114
|
-
[:voiced, :uvular, :flap].to_set
|
115
|
-
[:voiced, :epiglottal, :flap].to_set
|
116
|
-
[:alveolar, :
|
117
|
-
[:voiced, :alveolar, :
|
118
|
-
[:voiced, :alveolar, :
|
119
|
-
[:voiced, :retroflex, :
|
120
|
-
[:voiced, :palatal, :
|
121
|
-
[:voiced, :velar, :
|
122
|
-
[:voiced, :alveolar, :
|
123
|
-
[:voiced, :palatal, :
|
124
|
-
[:voiced, :velar, :
|
125
|
-
[:voiced, :close, :front].to_set
|
126
|
-
[:voiced, :close, :front, :rounded].to_set
|
127
|
-
[:voiced, :close, :central].to_set
|
128
|
-
[:voiced, :close, :central, :rounded].to_set
|
129
|
-
[:voiced, :close, :back].to_set
|
130
|
-
[:voiced, :close, :back, :rounded].to_set
|
131
|
-
[:voiced, :near_close, :near_front].to_set
|
132
|
-
[:voiced, :near_close, :near_front, :rounded].to_set
|
133
|
-
[:voiced, :near_close, :near_back, :rounded].to_set
|
134
|
-
[:voiced, :close_mid, :front].to_set
|
135
|
-
[:voiced, :close_mid, :front, :rounded].to_set
|
136
|
-
[:voiced, :close_mid, :central].to_set
|
137
|
-
[:voiced, :close_mid, :central, :rounded].to_set
|
138
|
-
[:voiced, :close_mid, :back].to_set
|
139
|
-
[:voiced, :close_mid, :back, :rounded].to_set
|
140
|
-
[:voiced, :mid, :central].to_set
|
141
|
-
[:voiced, :open_mid, :front].to_set
|
142
|
-
[:voiced, :open_mid, :front, :rounded].to_set
|
143
|
-
[:voiced, :open_mid, :central].to_set
|
144
|
-
[:voiced, :open_mid, :central, :rounded].to_set
|
145
|
-
[:voiced, :open_mid, :back].to_set
|
146
|
-
[:voiced, :open_mid, :back, :rounded].to_set
|
147
|
-
[:voiced, :near_open, :front].to_set
|
148
|
-
[:voiced, :near_open, :central].to_set
|
149
|
-
[:voiced, :open, :front].to_set
|
150
|
-
[:voiced, :open, :front, :rounded].to_set
|
151
|
-
[:voiced, :open, :back].to_set
|
152
|
-
[:voiced, :open, :back, :rounded].to_set
|
153
|
-
|
154
|
-
[:voiced, :
|
155
|
-
|
156
|
-
|
157
|
-
[:voiced, :
|
158
|
-
[:
|
159
|
-
[:voiced, :
|
63
|
+
[:voiced, :bilabial, :nasal].to_set => [0x006d],
|
64
|
+
[:voiced, :labiodental, :nasal].to_set => [0x0271],
|
65
|
+
[:voiced, :alveolar, :nasal].to_set => [0x006e],
|
66
|
+
[:voiced, :retroflex, :nasal].to_set => [0x0273],
|
67
|
+
[:voiced, :palatal, :nasal].to_set => [0x0272],
|
68
|
+
[:voiced, :velar, :nasal].to_set => [0x014b],
|
69
|
+
[:voiced, :uvular, :nasal].to_set => [0x0274],
|
70
|
+
[:bilabial, :plosive].to_set => [0x0070],
|
71
|
+
[:voiced, :bilabial, :plosive].to_set => [0x0062],
|
72
|
+
[:labiodental, :plosive].to_set => [0x0070, 0x032a],
|
73
|
+
[:voiced, :labiodental, :plosive].to_set => [0x0062, 0x032a],
|
74
|
+
[:alveolar, :plosive].to_set => [0x0074],
|
75
|
+
[:voiced, :alveolar, :plosive].to_set => [0x0064],
|
76
|
+
[:retroflex, :plosive].to_set => [0x0288],
|
77
|
+
[:voiced, :retroflex, :plosive].to_set => [0x0256],
|
78
|
+
[:palatal, :plosive].to_set => [0x0063],
|
79
|
+
[:voiced, :palatal, :plosive].to_set => [0x025f],
|
80
|
+
[:velar, :plosive].to_set => [0x006b],
|
81
|
+
[:voiced, :velar, :plosive].to_set => [0x0261],
|
82
|
+
[:uvular, :plosive].to_set => [0x0071],
|
83
|
+
[:voiced, :uvular, :plosive].to_set => [0x0262],
|
84
|
+
[:voiced, :epiglottal, :plosive].to_set => [0x02a1],
|
85
|
+
[:voiced, :glottal, :plosive].to_set => [0x0294],
|
86
|
+
[:bilabial, :fricative].to_set => [0x0278],
|
87
|
+
[:voiced, :bilabial, :fricative].to_set => [0x03b2],
|
88
|
+
[:labiodental, :fricative].to_set => [0x0066],
|
89
|
+
[:voiced, :labiodental, :fricative].to_set => [0x0076],
|
90
|
+
[:dental, :fricative].to_set => [0x03b8],
|
91
|
+
[:voiced, :dental, :fricative].to_set => [0x00f0],
|
92
|
+
[:alveolar, :fricative].to_set => [0x0073],
|
93
|
+
[:voiced, :alveolar, :fricative].to_set => [0x007a],
|
94
|
+
[:postalveolar, :fricative].to_set => [0x0283],
|
95
|
+
[:voiced, :postalveolar, :fricative].to_set => [0x0292],
|
96
|
+
[:retroflex, :fricative].to_set => [0x0282],
|
97
|
+
[:voiced, :retroflex, :fricative].to_set => [0x0290],
|
98
|
+
[:palatal, :fricative].to_set => [0x00e7],
|
99
|
+
[:voiced, :palatal, :fricative].to_set => [0x029d],
|
100
|
+
[:velar, :fricative].to_set => [0x0078],
|
101
|
+
[:voiced, :velar, :fricative].to_set => [0x0263],
|
102
|
+
[:uvular, :fricative].to_set => [0x03c7],
|
103
|
+
[:voiced, :uvular, :fricative, :approximant].to_set => [0x0281],
|
104
|
+
[:pharyngeal, :fricative].to_set => [0x0127],
|
105
|
+
[:voiced, :pharyngeal, :fricative, :approximant].to_set => [0x0295],
|
106
|
+
[:epiglottal, :fricative].to_set => [0x029c],
|
107
|
+
[:voiced, :epiglottal, :fricative, :approximant].to_set => [0x02a2],
|
108
|
+
[:glottal, :fricative, :approximant].to_set => [0x0068],
|
109
|
+
[:voiced, :glottal, :fricative, :approximant].to_set => [0x0266],
|
110
|
+
[:voiced, :bilabial, :approximant].to_set => [0x03b2, 0x031e],
|
111
|
+
[:voiced, :labiodental, :approximant].to_set => [0x028b],
|
112
|
+
[:voiced, :alveolar, :approximant].to_set => [0x0279],
|
113
|
+
[:voiced, :retroflex, :approximant].to_set => [0x027b],
|
114
|
+
[:voiced, :palatal, :approximant].to_set => [0x006a],
|
115
|
+
[:voiced, :velar, :approximant].to_set => [0x0270],
|
116
|
+
[:voiced, :bilabial, :trill].to_set => [0x0299],
|
117
|
+
[:voiced, :alveolar, :trill].to_set => [0x0072],
|
118
|
+
[:voiced, :uvular, :trill].to_set => [0x0280],
|
119
|
+
[:voiced, :epiglottal, :trill].to_set => [0x044f],
|
120
|
+
[:voiced, :bilabial, :flap].to_set => [0x2c71, 0x031f],
|
121
|
+
[:voiced, :labiodental, :flap].to_set => [0x2c71],
|
122
|
+
[:voiced, :alveolar, :flap].to_set => [0x027e],
|
123
|
+
[:voiced, :retroflex, :flap].to_set => [0x027d],
|
124
|
+
[:voiced, :uvular, :flap].to_set => [0x0262, 0x0306],
|
125
|
+
[:voiced, :epiglottal, :flap].to_set => [0x02a1, 0x032f],
|
126
|
+
[:alveolar, :lateral_fricative].to_set => [0x026c],
|
127
|
+
[:voiced, :alveolar, :lateral_fricative].to_set => [0x026e],
|
128
|
+
[:voiced, :alveolar, :lateral_approximant].to_set => [0x006c],
|
129
|
+
[:voiced, :retroflex, :lateral_approximant].to_set => [0x026d],
|
130
|
+
[:voiced, :palatal, :lateral_approximant].to_set => [0x028e],
|
131
|
+
[:voiced, :velar, :lateral_approximant].to_set => [0x029f],
|
132
|
+
[:voiced, :alveolar, :lateral_flap].to_set => [0x027a],
|
133
|
+
[:voiced, :palatal, :lateral_flap].to_set => [0x028e, 0x032f],
|
134
|
+
[:voiced, :velar, :lateral_flap].to_set => [0x029f, 0x0306],
|
135
|
+
[:voiced, :close, :front].to_set => [0x0069],
|
136
|
+
[:voiced, :close, :front, :rounded].to_set => [0x0079],
|
137
|
+
[:voiced, :close, :central].to_set => [0x0268],
|
138
|
+
[:voiced, :close, :central, :rounded].to_set => [0x0289],
|
139
|
+
[:voiced, :close, :back].to_set => [0x026f],
|
140
|
+
[:voiced, :close, :back, :rounded].to_set => [0x0075],
|
141
|
+
[:voiced, :near_close, :near_front].to_set => [0x026a],
|
142
|
+
[:voiced, :near_close, :near_front, :rounded].to_set => [0x028f],
|
143
|
+
[:voiced, :near_close, :near_back, :rounded].to_set => [0x028a],
|
144
|
+
[:voiced, :close_mid, :front].to_set => [0x0065],
|
145
|
+
[:voiced, :close_mid, :front, :rounded].to_set => [0x00f8],
|
146
|
+
[:voiced, :close_mid, :central].to_set => [0x0258],
|
147
|
+
[:voiced, :close_mid, :central, :rounded].to_set => [0x0275],
|
148
|
+
[:voiced, :close_mid, :back].to_set => [0x0264],
|
149
|
+
[:voiced, :close_mid, :back, :rounded].to_set => [0x006f],
|
150
|
+
[:voiced, :mid, :central].to_set => [0x0259],
|
151
|
+
[:voiced, :open_mid, :front].to_set => [0x025b],
|
152
|
+
[:voiced, :open_mid, :front, :rounded].to_set => [0x0153],
|
153
|
+
[:voiced, :open_mid, :central].to_set => [0x025c],
|
154
|
+
[:voiced, :open_mid, :central, :rounded].to_set => [0x025e],
|
155
|
+
[:voiced, :open_mid, :back].to_set => [0x028c],
|
156
|
+
[:voiced, :open_mid, :back, :rounded].to_set => [0x0254],
|
157
|
+
[:voiced, :near_open, :front].to_set => [0x00e6],
|
158
|
+
[:voiced, :near_open, :central].to_set => [0x0250],
|
159
|
+
[:voiced, :open, :front].to_set => [0x0061],
|
160
|
+
[:voiced, :open, :front, :rounded].to_set => [0x0276],
|
161
|
+
[:voiced, :open, :back].to_set => [0x0251],
|
162
|
+
[:voiced, :open, :back, :rounded].to_set => [0x0252],
|
163
|
+
[:voiced, :retroflex, :lateral_fricative].to_set => [0xa78e],
|
164
|
+
[:voiced, :palatal, :lateral_fricative].to_set => [0xf267],
|
165
|
+
[:voiced, :velar, :lateral_fricative].to_set => [0xf268],
|
166
|
+
[:voiced, :retroflex, :lateral_flap].to_set => [0xf269],
|
167
|
+
[:voiced, :bilabial, :velar, :approximant].to_set => [0x0077],
|
168
|
+
[:bilabial, :velar, :approximant].to_set => [0x268d],
|
169
|
+
[:voiced, :bilabial, :palatal, :approximant].to_set => [0x0265],
|
170
|
+
[:voiced, :velar, :alveolar, :lateral_approximant].to_set => [0x026b],
|
171
|
+
[:palatal, :alveolar, :fricative].to_set => [0x0255],
|
172
|
+
[:voiced, :palatal, :alveolar, :fricative].to_set => [0x0291],
|
173
|
+
[:palatal, :velar, :fricative].to_set => [0x0267]
|
160
174
|
}
|
175
|
+
SETS.each {|k, v| k.freeze}
|
161
176
|
|
162
177
|
class << self
|
163
178
|
# Return the set corresponding to the kind of feature: manner or place of
|
@@ -171,11 +186,19 @@ module Phonology
|
|
171
186
|
# corresponding features. If a single feature is given, just return the
|
172
187
|
# feature.
|
173
188
|
def expand(*args)
|
174
|
-
args = args.
|
189
|
+
args = args.first.kind_of?(Set) ? args.shift.to_a : args.flatten
|
190
|
+
args.inject([].to_set) do |memo, obj|
|
175
191
|
memo << (CLASSES[obj] || obj)
|
176
192
|
end.flatten.to_set
|
177
193
|
end
|
178
194
|
|
195
|
+
def place_groups(*args)
|
196
|
+
args = args.first.kind_of?(Set) ? args.shift : args.flatten.to_set
|
197
|
+
[:labial, :coronal, :dorsal, :radical].map do |fclass|
|
198
|
+
fclass unless (CLASSES[fclass] & args).empty?
|
199
|
+
end.compact.uniq
|
200
|
+
end
|
201
|
+
|
179
202
|
end
|
180
203
|
|
181
204
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Phonology
|
2
2
|
|
3
3
|
# An inventory of Phonological feature sets.
|
4
|
-
class
|
4
|
+
class Inventory
|
5
5
|
|
6
6
|
# A Hash with a set of features as keys, and UTF-8 codepoints for IPA
|
7
7
|
# letters as values.
|
@@ -32,38 +32,58 @@ module Phonology
|
|
32
32
|
end
|
33
33
|
|
34
34
|
# Given a set of features, return an array of UTF-8 codepoints.
|
35
|
-
def codepoints(features)
|
36
|
-
features = features
|
35
|
+
def codepoints(*features)
|
36
|
+
features = setify(*features)
|
37
37
|
@sets[features] || (raise FeatureError, "No such set #{features.inspect}")
|
38
38
|
end
|
39
39
|
|
40
40
|
# Return an instance of Sounds whose sets include any of the given
|
41
41
|
# features.
|
42
42
|
def with(*features)
|
43
|
-
|
44
|
-
self.class.new
|
43
|
+
pos, neg = mangle_args(*features)
|
44
|
+
self.class.new(@sets.select do |key, val|
|
45
|
+
!key.intersection(pos).empty?
|
46
|
+
end).without_any(neg)
|
45
47
|
end
|
46
48
|
|
47
49
|
# Return feature sets that include all of the given features
|
48
50
|
def with_all(*features)
|
49
|
-
|
50
|
-
self.class.new
|
51
|
+
pos, neg = mangle_args(*features)
|
52
|
+
self.class.new(@sets.select do |key, val|
|
53
|
+
pos.subset?(key)
|
54
|
+
end).without_any(neg)
|
51
55
|
end
|
52
56
|
|
53
57
|
# Return an instance of Sounds whose sets exclude any of the given
|
54
58
|
# features.
|
55
59
|
def without(*features)
|
56
|
-
features = features
|
60
|
+
features = setify(*features)
|
57
61
|
self.class.new @sets.select {|key, val| !features.subset?(key)}
|
58
62
|
end
|
59
63
|
|
60
64
|
# Return an instance of Sounds whose sets exclude all of the given
|
61
65
|
# features.
|
62
66
|
def without_any(*features)
|
63
|
-
features = features
|
67
|
+
features = setify(*features)
|
64
68
|
self.class.new @sets.select {|key, val| key.intersection(features).empty?}
|
65
69
|
end
|
66
70
|
|
71
|
+
private
|
72
|
+
|
73
|
+
def mangle_args(*args)
|
74
|
+
pos = setify(*args)
|
75
|
+
neg = extract_negations(pos)
|
76
|
+
[pos.select {|f| f !~ /\A(non_|un)/}.compact.to_set, neg]
|
77
|
+
end
|
78
|
+
|
79
|
+
def setify(*args)
|
80
|
+
Features.expand(args.first.kind_of?(Set) ? args.shift : args.flatten.to_set)
|
81
|
+
end
|
82
|
+
|
83
|
+
def extract_negations(set)
|
84
|
+
Features.expand(*set.map {|feat| feat =~ /\A(non_|un)([a-z_]*)\z/ && $2.to_sym}.compact)
|
85
|
+
end
|
86
|
+
|
67
87
|
end
|
68
88
|
|
69
89
|
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Phonology
|
2
|
+
|
3
|
+
module OrthographyTranslatorDSL
|
4
|
+
|
5
|
+
attr_accessor :last_sound, :anticipation
|
6
|
+
|
7
|
+
def method_missing(sym, *args, &block)
|
8
|
+
last_sound.__send__(sym, *args, &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def anticipate(&block)
|
12
|
+
@anticipation = block
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def curr_char
|
17
|
+
array[@index]
|
18
|
+
end
|
19
|
+
|
20
|
+
def next_char(offset = 1)
|
21
|
+
array[@index + offset]
|
22
|
+
end
|
23
|
+
|
24
|
+
def prev_char(offset = 1)
|
25
|
+
array[@index - offset] unless @index == 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def follows(*chars)
|
29
|
+
chars.flatten.each {|c| return true if c == prev_char}
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
def precedes(*chars)
|
34
|
+
return false if !next_char
|
35
|
+
chars.flatten.each {|c| return true if c == next_char}
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
def between(before, after)
|
40
|
+
follows(*before) && precedes(*after)
|
41
|
+
end
|
42
|
+
|
43
|
+
def initial?
|
44
|
+
@index == 0
|
45
|
+
end
|
46
|
+
|
47
|
+
def final?
|
48
|
+
!next_char
|
49
|
+
end
|
50
|
+
|
51
|
+
def get(*features)
|
52
|
+
if features.first.kind_of?(Array)
|
53
|
+
get_affricate(*features)
|
54
|
+
else
|
55
|
+
match = sounds.with_all(*features)
|
56
|
+
return nil if match.sets.empty? || match.sets.length > 1
|
57
|
+
@last_sound = Sound.new *match.sets.keys.first.to_a
|
58
|
+
end
|
59
|
+
@last_sound.orthography = curr_char
|
60
|
+
do_anticipation
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def get_affricate(*features)
|
66
|
+
@last_sound = Phonology::Affricate.new(features[0], features[1])
|
67
|
+
end
|
68
|
+
|
69
|
+
def do_anticipation
|
70
|
+
if @anticipation
|
71
|
+
@anticipation.call(last_sound)
|
72
|
+
@anticipation = nil
|
73
|
+
end
|
74
|
+
return last_sound
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
class OrthographyTranslator
|
80
|
+
|
81
|
+
attr_accessor :rules, :scanner, :sounds, :string
|
82
|
+
|
83
|
+
include OrthographyTranslatorDSL
|
84
|
+
|
85
|
+
# Translate orthorgraphy to IPA
|
86
|
+
def translate(string)
|
87
|
+
@string = string
|
88
|
+
@max = array.length
|
89
|
+
SoundSequence.new(array.each_index.map do |index|
|
90
|
+
@index = index
|
91
|
+
instance_eval(&@rules)
|
92
|
+
end.flatten.compact)
|
93
|
+
ensure
|
94
|
+
@max = 0
|
95
|
+
@string = nil
|
96
|
+
@array = nil
|
97
|
+
@index = nil
|
98
|
+
@last_sound = nil
|
99
|
+
end
|
100
|
+
|
101
|
+
def set_rules(&block)
|
102
|
+
@rules = block
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def array
|
108
|
+
@array ||= scan
|
109
|
+
end
|
110
|
+
|
111
|
+
def scan
|
112
|
+
scanner ? scanner.call(string) : string.split(//u)
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Phonology
|
2
|
+
|
3
|
+
class RulesDSL
|
4
|
+
|
5
|
+
attr :array, :index
|
6
|
+
|
7
|
+
def initialize(array)
|
8
|
+
@array = array
|
9
|
+
end
|
10
|
+
|
11
|
+
def method_missing(sym, *args, &block)
|
12
|
+
curr_sound.__send__(sym, *args, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def apply(&block)
|
16
|
+
array.each_index.map do |index|
|
17
|
+
@index = index
|
18
|
+
instance_eval(&block)
|
19
|
+
get_result
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def delete(*args)
|
24
|
+
if !args.empty?
|
25
|
+
curr_sound.delete(*args)
|
26
|
+
else
|
27
|
+
array[@index] = nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def insert(*args)
|
32
|
+
@result = [curr_sound, Sound.new(*args)]
|
33
|
+
end
|
34
|
+
|
35
|
+
def curr_sound
|
36
|
+
array[@index]
|
37
|
+
end
|
38
|
+
|
39
|
+
def next_sound(offset = 1)
|
40
|
+
!final? && array[@index + offset]
|
41
|
+
end
|
42
|
+
|
43
|
+
def prev_sound(offset = 1)
|
44
|
+
!initial? && array[@index - offset]
|
45
|
+
end
|
46
|
+
|
47
|
+
def voice
|
48
|
+
curr_sound << :voiced
|
49
|
+
end
|
50
|
+
|
51
|
+
def devoice
|
52
|
+
curr_sound >> :voiced
|
53
|
+
end
|
54
|
+
|
55
|
+
def precedes(*features)
|
56
|
+
return false if !next_sound
|
57
|
+
features.flatten.each {|f| return false unless next_sound.send(:"#{f}?")}
|
58
|
+
true
|
59
|
+
end
|
60
|
+
|
61
|
+
def follows(*features)
|
62
|
+
return false if initial?
|
63
|
+
features.flatten.each {|f| return false unless prev_sound.send(:"#{f}?")}
|
64
|
+
true
|
65
|
+
end
|
66
|
+
|
67
|
+
def initial?
|
68
|
+
@index == 0
|
69
|
+
end
|
70
|
+
|
71
|
+
def final?
|
72
|
+
@index == @max
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def get_result
|
78
|
+
if !@result
|
79
|
+
curr_sound
|
80
|
+
else
|
81
|
+
result = @result
|
82
|
+
@result = nil
|
83
|
+
result
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
class Rule
|
90
|
+
|
91
|
+
attr :rule
|
92
|
+
|
93
|
+
def initialize(&block)
|
94
|
+
@rule = block
|
95
|
+
end
|
96
|
+
|
97
|
+
def apply(sounds)
|
98
|
+
dsl = RulesDSL.new(sounds)
|
99
|
+
dsl.apply(&rule).flatten
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
data/lib/phonology/sound.rb
CHANGED
@@ -1,22 +1,18 @@
|
|
1
1
|
module Phonology
|
2
2
|
|
3
|
-
|
4
|
-
class Sound
|
3
|
+
module SoundBase
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
def initialize(*features)
|
9
|
-
@features = if features.first.kind_of?(String)
|
10
|
-
Phonology.features(features.shift)
|
11
|
-
else
|
12
|
-
features.to_set
|
13
|
-
end
|
14
|
-
end
|
5
|
+
attr_accessor :features, :orthography, :hints
|
6
|
+
protected :features=, :hints=
|
15
7
|
|
16
8
|
Features::ALL.each do |feature|
|
17
9
|
class_eval(<<-EOM, __FILE__, __LINE__ +1)
|
18
10
|
def #{feature}?
|
19
|
-
|
11
|
+
features.include? :#{feature}
|
12
|
+
end
|
13
|
+
|
14
|
+
def non_#{feature}?
|
15
|
+
!#{feature}?
|
20
16
|
end
|
21
17
|
EOM
|
22
18
|
end
|
@@ -25,79 +21,176 @@ module Phonology
|
|
25
21
|
class_eval(<<-EOM, __FILE__, __LINE__ +1)
|
26
22
|
def #{feature_class}?
|
27
23
|
set = Features.expand(:#{feature_class})
|
28
|
-
!set.intersection(
|
24
|
+
!set.intersection(features).empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
def non_#{feature_class}?
|
28
|
+
!#{feature_class}?
|
29
29
|
end
|
30
30
|
EOM
|
31
31
|
end
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
alias unvoiced? non_voiced?
|
34
|
+
|
35
|
+
def sonority
|
36
|
+
case
|
37
|
+
when vocalic? then 5
|
38
|
+
when approximant? then 4
|
39
|
+
when liquid? then 3
|
40
|
+
when nasal? then 2
|
41
|
+
when fricative? then 1
|
42
|
+
else 0
|
43
|
+
end
|
36
44
|
end
|
37
45
|
|
38
|
-
#
|
39
|
-
|
40
|
-
|
41
|
-
feature = feature.to_sym
|
42
|
-
(@features -= (Features.set(feature) || [])) << feature
|
43
|
-
self
|
46
|
+
# Orthography hints that can be useful to consult when applying rules.
|
47
|
+
def hints
|
48
|
+
@hints ||= []
|
44
49
|
end
|
45
|
-
alias add <<
|
46
50
|
|
47
|
-
#
|
48
|
-
def
|
49
|
-
|
51
|
+
# TODO set up list of valid hints
|
52
|
+
def hint(*args)
|
53
|
+
self.hints += args.flatten
|
50
54
|
self
|
51
55
|
end
|
52
56
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
@features = features + [feature]
|
64
|
-
return self
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
self
|
57
|
+
def orthography
|
58
|
+
@orthography ||= ""
|
59
|
+
end
|
60
|
+
|
61
|
+
def codepoints
|
62
|
+
raise NotImplementedError
|
63
|
+
end
|
64
|
+
|
65
|
+
def exists?
|
66
|
+
raise NotImplementedError
|
69
67
|
end
|
70
68
|
|
71
|
-
|
69
|
+
# Get the IPA codepoints for the sound, including any diacritics.
|
70
|
+
def symbol
|
71
|
+
codepoints.pack("U*")
|
72
|
+
end
|
73
|
+
|
74
|
+
# Does the sound have more than one place of articulation?
|
75
|
+
def coarticulated?
|
76
|
+
place.size > 1
|
72
77
|
end
|
73
78
|
|
74
79
|
# Get the sound's place of articulation.
|
75
80
|
def place
|
76
|
-
|
81
|
+
features.intersection(Features::PLACE)
|
77
82
|
end
|
78
83
|
|
79
84
|
# Get the sound's manner of articulation.
|
80
85
|
def manner
|
81
|
-
|
86
|
+
features.intersection(Features::MANNER)
|
82
87
|
end
|
83
88
|
|
84
89
|
# Get the sound's height.
|
85
90
|
def height
|
86
|
-
|
91
|
+
features.intersection(Features::HEIGHT)
|
87
92
|
end
|
88
93
|
|
89
94
|
# Get the sound's backness.
|
90
95
|
def backness
|
91
|
-
|
96
|
+
features.intersection(Features::BACKNESS)
|
92
97
|
end
|
93
98
|
|
94
|
-
|
95
|
-
|
99
|
+
# Get the place groups (:coronal, :dorsal, etc). Normally there should only be one.
|
100
|
+
def place_groups
|
101
|
+
Features.place_groups(features)
|
96
102
|
end
|
97
103
|
|
104
|
+
end
|
105
|
+
|
106
|
+
# A set of distinctive features
|
107
|
+
class Sound
|
108
|
+
|
109
|
+
include SoundBase
|
110
|
+
|
111
|
+
# Does the group of features exist in human speech?
|
98
112
|
def self.exists?(features)
|
99
113
|
!! Features::SETS[features]
|
100
114
|
end
|
101
115
|
|
116
|
+
def initialize(*features)
|
117
|
+
if features.first.kind_of?(String)
|
118
|
+
self.features = Phonology.features_for(features.shift).clone
|
119
|
+
else
|
120
|
+
self.features = features.to_set
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Get the IPA codepoints for the sound, excluding any diacritics.
|
125
|
+
def codepoints
|
126
|
+
Phonology.sounds.codepoints(features)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Add a feature, replacing either the place or manner of articulation,
|
130
|
+
# or the height or backness. Returns self.
|
131
|
+
def <<(*args)
|
132
|
+
args.to_a.flatten.each do |feature|
|
133
|
+
features.subtract Features.set(feature).to_a
|
134
|
+
add! feature
|
135
|
+
end
|
136
|
+
self
|
137
|
+
end
|
138
|
+
alias add <<
|
139
|
+
|
140
|
+
# Add a feature without replacing place or manner.
|
141
|
+
def add!(feature)
|
142
|
+
features.add feature.to_sym
|
143
|
+
self
|
144
|
+
end
|
145
|
+
|
146
|
+
# Remove a feature, and return self.
|
147
|
+
def >>(*args)
|
148
|
+
args.to_a.flatten.each do |feature|
|
149
|
+
features.delete feature.to_sym
|
150
|
+
end
|
151
|
+
self
|
152
|
+
end
|
153
|
+
alias delete >>
|
154
|
+
|
155
|
+
# Does the sound exist?
|
156
|
+
def exists?
|
157
|
+
self.class.exists?(features)
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
# A sound which begins plosive and finished fricative.
|
164
|
+
class Affricate
|
165
|
+
|
166
|
+
include SoundBase
|
167
|
+
|
168
|
+
attr_accessor :onset, :release
|
169
|
+
protected :onset=, :release=
|
170
|
+
|
171
|
+
def initialize(onset, release)
|
172
|
+
self.onset = get_sound(onset)
|
173
|
+
self.release = get_sound(release)
|
174
|
+
end
|
175
|
+
|
176
|
+
def exists?
|
177
|
+
onset.exists? && release.exists?
|
178
|
+
end
|
179
|
+
|
180
|
+
def features
|
181
|
+
onset.features + release.features
|
182
|
+
end
|
183
|
+
|
184
|
+
def codepoints
|
185
|
+
[onset.codepoints, Features::DIACRITICS[:affricate], release.codepoints].flatten
|
186
|
+
end
|
187
|
+
|
188
|
+
private
|
189
|
+
|
190
|
+
def get_sound(arg)
|
191
|
+
arg.kind_of?(Sound) ? arg : Sound.new(*arg)
|
192
|
+
end
|
193
|
+
|
102
194
|
end
|
195
|
+
|
103
196
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Phonology
|
2
|
+
|
3
|
+
# A collection of sounds
|
4
|
+
class SoundSequence
|
5
|
+
|
6
|
+
attr :sounds
|
7
|
+
|
8
|
+
def method_missing(sym, *args, &block)
|
9
|
+
@sounds.__send__(sym, *args, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(arg)
|
13
|
+
# String of ipa symbols
|
14
|
+
if arg.kind_of? String
|
15
|
+
@sounds = arg.split('').map {|letter| Sound.new(letter)}
|
16
|
+
else
|
17
|
+
@sounds = arg.to_a
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def symbols
|
22
|
+
compact.map(&:symbol).join
|
23
|
+
end
|
24
|
+
alias ipa symbols
|
25
|
+
alias to_s symbols
|
26
|
+
alias to_str symbols
|
27
|
+
|
28
|
+
def orthography
|
29
|
+
compact.map(&:orthography).join
|
30
|
+
end
|
31
|
+
|
32
|
+
# Apply rule and get a new instance of SoundSequence.
|
33
|
+
def apply_rule(rule)
|
34
|
+
self.class.new rule.apply(@sounds)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Apply rule in place.
|
38
|
+
def apply_rule!(rule)
|
39
|
+
@sounds = rule.apply(@sounds)
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
data/lib/phonology/version.rb
CHANGED
data/lib/phonology.rb
CHANGED
@@ -1,21 +1,24 @@
|
|
1
1
|
require "set"
|
2
2
|
require File.expand_path("../phonology/features", __FILE__)
|
3
|
-
require File.expand_path("../phonology/
|
3
|
+
require File.expand_path("../phonology/inventory", __FILE__)
|
4
4
|
require File.expand_path("../phonology/sound", __FILE__)
|
5
|
+
require File.expand_path("../phonology/sound_sequence", __FILE__)
|
6
|
+
require File.expand_path("../phonology/orthography", __FILE__)
|
7
|
+
require File.expand_path("../phonology/rule", __FILE__)
|
5
8
|
|
6
9
|
module Phonology
|
7
10
|
|
8
11
|
extend self
|
9
12
|
|
10
13
|
def sounds
|
11
|
-
@sounds ||=
|
14
|
+
@sounds ||= Inventory.new(Features::SETS).freeze
|
12
15
|
end
|
13
16
|
|
14
|
-
def
|
17
|
+
def symbol_for(*args)
|
15
18
|
sounds.symbol(*args)
|
16
19
|
end
|
17
20
|
|
18
|
-
def
|
21
|
+
def features_for(*args)
|
19
22
|
sounds.features(*args)
|
20
23
|
end
|
21
24
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.expand_path("../test_helper", __FILE__)
|
2
|
+
|
3
|
+
class AffricateTest < Test::Unit::TestCase
|
4
|
+
include Phonology
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@affricate = Affricate.new("t", "s")
|
8
|
+
end
|
9
|
+
|
10
|
+
test "should get symbol for affricate" do
|
11
|
+
assert_equal [116, 865, 115], @affricate.symbol.unpack("U*")
|
12
|
+
end
|
13
|
+
|
14
|
+
test "should act like a sound" do
|
15
|
+
assert @affricate.exists?
|
16
|
+
assert @affricate.coronal?
|
17
|
+
assert @affricate.plosive?
|
18
|
+
assert @affricate.fricative?
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
data/test/features_test.rb
CHANGED
@@ -2,9 +2,17 @@ require File.expand_path("../test_helper", __FILE__)
|
|
2
2
|
|
3
3
|
class FeaturesTest < Test::Unit::TestCase
|
4
4
|
|
5
|
-
|
5
|
+
include Phonology
|
6
|
+
|
7
|
+
test "should expand place groups" do
|
6
8
|
assert_equal [:dental, :alveolar, :postalveolar, :retroflex, :velar].to_set,
|
7
|
-
|
9
|
+
Features.expand(:coronal, :dental, :velar)
|
10
|
+
end
|
11
|
+
|
12
|
+
test "should contract place groups" do
|
13
|
+
assert_equal [:coronal], Features.place_groups([:alveolar, :plosive, :postalveolar, :fricative])
|
14
|
+
assert_equal [:coronal], Features.place_groups([:alveolar, :plosive, :postalveolar, :fricative].to_set)
|
15
|
+
assert_equal [:coronal], Features.place_groups(:alveolar, :plosive, :postalveolar, :fricative)
|
8
16
|
end
|
9
17
|
|
10
18
|
end
|
@@ -7,7 +7,7 @@ class SoundsTest < Test::Unit::TestCase
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def small_set
|
10
|
-
Phonology::
|
10
|
+
Phonology::Inventory.from_ipa("b", "p", "d", "t", "m", "n")
|
11
11
|
end
|
12
12
|
|
13
13
|
test "should return feature set for symbol" do
|
@@ -29,4 +29,17 @@ class SoundsTest < Test::Unit::TestCase
|
|
29
29
|
test "#without_any should return a set of sets that exclude all of the given features" do
|
30
30
|
assert_equal ["d", "t"].to_set, small_set.without_any(:bilabial, :nasal).symbols
|
31
31
|
end
|
32
|
+
|
33
|
+
test "should expand features" do
|
34
|
+
assert_equal ["t", "d", "n"].to_set, small_set.with(:coronal).symbols
|
35
|
+
end
|
36
|
+
|
37
|
+
test "#with should exclude negative features" do
|
38
|
+
assert_equal ["t", "d"].to_set, small_set.with(:coronal, :non_nasal).symbols
|
39
|
+
end
|
40
|
+
|
41
|
+
test "#with_all should exclude negative features" do
|
42
|
+
assert_equal ["t"].to_set, small_set.with_all(:alveolar, :unvoiced).symbols
|
43
|
+
end
|
44
|
+
|
32
45
|
end
|
data/test/phonology_test.rb
CHANGED
@@ -2,16 +2,16 @@ require File.expand_path("../test_helper", __FILE__)
|
|
2
2
|
|
3
3
|
class PhonologyTest < Test::Unit::TestCase
|
4
4
|
|
5
|
-
test "sounds should return an instance of
|
6
|
-
assert_equal Phonology::
|
5
|
+
test "sounds should return an instance of Inventory" do
|
6
|
+
assert_equal Phonology::Inventory, Phonology.sounds.class
|
7
7
|
end
|
8
8
|
|
9
9
|
test "should return feature set for symbol" do
|
10
|
-
assert_equal [:voiced, :bilabial, :nasal].to_set, Phonology.
|
10
|
+
assert_equal [:voiced, :bilabial, :nasal].to_set, Phonology.features_for("m")
|
11
11
|
end
|
12
12
|
|
13
13
|
test "should return a symbol for a feature set" do
|
14
|
-
assert_equal "d", Phonology.
|
14
|
+
assert_equal "d", Phonology.symbol_for(:voiced, :alveolar, :plosive)
|
15
15
|
end
|
16
16
|
|
17
17
|
end
|
data/test/rule_test.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.expand_path("../test_helper", __FILE__)
|
2
|
+
|
3
|
+
class RuleTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
include Phonology
|
6
|
+
|
7
|
+
test "should delegate to current sound" do
|
8
|
+
assert_rule "amta", "anta" do
|
9
|
+
add :alveolar if nasal? and precedes :alveolar
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
test "should delete sound" do
|
14
|
+
assert_rule "amta", "ama" do
|
15
|
+
delete if alveolar? and follows :nasal
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
test "should insert sound" do
|
20
|
+
assert_rule "pta", "pata" do
|
21
|
+
insert "a" if plosive? and precedes :plosive
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
test "should devoice" do
|
26
|
+
assert_rule "bta", "pta" do
|
27
|
+
devoice if plosive? and precedes(:plosive, :unvoiced)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def assert_rule(given, expected, &block)
|
34
|
+
rule = Rule.new &block
|
35
|
+
sounds = SoundSequence.new(given)
|
36
|
+
assert_equal expected, sounds.apply_rule!(rule).to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
end
|
data/test/sound_test.rb
CHANGED
@@ -4,6 +4,22 @@ class SoundTest < Test::Unit::TestCase
|
|
4
4
|
|
5
5
|
include Phonology
|
6
6
|
|
7
|
+
test "vowels should be more sonorous than approximants" do
|
8
|
+
assert Sound.new("o").sonority > Sound.new("w").sonority
|
9
|
+
end
|
10
|
+
|
11
|
+
test "approximants should be more sonorous than liquids" do
|
12
|
+
assert Sound.new("j").sonority > Sound.new("l").sonority
|
13
|
+
end
|
14
|
+
|
15
|
+
test "liquids should be more sonorous than nasals" do
|
16
|
+
assert Sound.new("l").sonority > Sound.new("n").sonority
|
17
|
+
end
|
18
|
+
|
19
|
+
test "nasals should be more sonorous than non-nasal consonants" do
|
20
|
+
assert Sound.new("n").sonority > Sound.new("z").sonority
|
21
|
+
end
|
22
|
+
|
7
23
|
test "should return an IPA symbol" do
|
8
24
|
assert_equal "m", Sound.new(:voiced, :bilabial, :nasal).symbol
|
9
25
|
end
|
@@ -60,6 +76,10 @@ class SoundTest < Test::Unit::TestCase
|
|
60
76
|
assert_equal :alveolar, Sound.new("r").place.first
|
61
77
|
end
|
62
78
|
|
79
|
+
test "should indicate if sound is coarticulated" do
|
80
|
+
assert Sound.new("w").coarticulated?
|
81
|
+
end
|
82
|
+
|
63
83
|
test "should get manner of articulation" do
|
64
84
|
assert_equal :trill, Sound.new("r").manner.first
|
65
85
|
end
|
@@ -72,13 +92,9 @@ class SoundTest < Test::Unit::TestCase
|
|
72
92
|
assert_equal :front, Sound.new("e").backness.first
|
73
93
|
end
|
74
94
|
|
75
|
-
test "should indicate if sound exists" do
|
95
|
+
test "should indicate if simple sound exists" do
|
76
96
|
assert !Sound.new(:front, :plosive).exists?
|
77
97
|
assert Sound.new(:bilabial, :plosive).exists?
|
78
98
|
end
|
79
99
|
|
80
|
-
test "should move sounds backwards" do
|
81
|
-
assert_equal "n", Sound.new("m").backward.backward.symbol
|
82
|
-
end
|
83
|
-
|
84
100
|
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 5
|
9
|
+
version: 0.0.5
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Norman Clarke
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-05-
|
17
|
+
date: 2010-05-20 00:00:00 -03:00
|
18
18
|
default_executable:
|
19
19
|
dependencies: []
|
20
20
|
|
@@ -29,13 +29,18 @@ extra_rdoc_files: []
|
|
29
29
|
files:
|
30
30
|
- lib/phonology.rb
|
31
31
|
- lib/phonology/features.rb
|
32
|
+
- lib/phonology/inventory.rb
|
33
|
+
- lib/phonology/orthography.rb
|
34
|
+
- lib/phonology/rule.rb
|
32
35
|
- lib/phonology/sound.rb
|
33
|
-
- lib/phonology/
|
36
|
+
- lib/phonology/sound_sequence.rb
|
34
37
|
- lib/phonology/version.rb
|
38
|
+
- test/affricate_test.rb
|
35
39
|
- test/features_test.rb
|
40
|
+
- test/inventory_test.rb
|
36
41
|
- test/phonology_test.rb
|
42
|
+
- test/rule_test.rb
|
37
43
|
- test/sound_test.rb
|
38
|
-
- test/sounds_test.rb
|
39
44
|
- test/test_helper.rb
|
40
45
|
- README.md
|
41
46
|
- LICENSE
|