line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#!/usr/bin/perl -w |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
package Lingua::Phonology::Features; |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
=head1 NAME |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
Lingua::Phonology::Features - a module to handle a set of hierarchical |
8
|
|
|
|
|
|
|
features. |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
=head1 SYNOPSIS |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
use Lingua::Phonology; |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
my $phono = new Lingua::Phonology; |
15
|
|
|
|
|
|
|
my $features = $phono->features; |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
# Add features programmatically |
18
|
|
|
|
|
|
|
$features->add_feature( |
19
|
|
|
|
|
|
|
Node => { type => 'privative', children => ['Scalar', 'Binary', 'Privative'] }, |
20
|
|
|
|
|
|
|
Scalar => { type => 'scalar' }, |
21
|
|
|
|
|
|
|
Binary => { type => 'binary' }, |
22
|
|
|
|
|
|
|
Privative => { type => 'privative' } |
23
|
|
|
|
|
|
|
); |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
# Drop features |
26
|
|
|
|
|
|
|
$features->drop_feature('Privative'); |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
# Load feature definitions from a file |
29
|
|
|
|
|
|
|
$features->loadfile('phono.xml'); |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
# Load default features |
32
|
|
|
|
|
|
|
$features->loadfile; |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
=head1 DESCRIPTION |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
Lingua::Phonology::Features allows you to create a hierarchy of features of |
38
|
|
|
|
|
|
|
various types, and includes methods for adding and deleting features and |
39
|
|
|
|
|
|
|
changing the relationships between them. |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
By "heirarchical features" we mean that some features dominate some other |
42
|
|
|
|
|
|
|
features, as in a tree. By having heirarchical features, it becomes possible to |
43
|
|
|
|
|
|
|
set multiple features at once by assigning to a node, and to indicate |
44
|
|
|
|
|
|
|
conceptually related features that are combined under the same node. This |
45
|
|
|
|
|
|
|
module, however, does not instantiate values of features, but only establishes |
46
|
|
|
|
|
|
|
the relationships between features. |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
Lingua::Phonology::Features recognizes multiple types of features. Features |
49
|
|
|
|
|
|
|
may be privative (which means that their legal values are either true or |
50
|
|
|
|
|
|
|
C), binary (which means they may be true, false, or C), or scalar |
51
|
|
|
|
|
|
|
(which means that their legal value may be anything). You can freely mix |
52
|
|
|
|
|
|
|
different kinds of features into the same set of features. |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
Finally, while this module provides a full set of methods to add and delete |
55
|
|
|
|
|
|
|
features programmatically, it also provides the option of reading feature |
56
|
|
|
|
|
|
|
definitions from a file. This is usually faster and more convenient. The |
57
|
|
|
|
|
|
|
method to do this is L<"loadfile">. Lingua::Phonology::Features also comes |
58
|
|
|
|
|
|
|
with an extensive default feature set. |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
=cut |
61
|
|
|
|
|
|
|
|
62
|
2
|
|
|
2
|
|
44287
|
use strict; |
|
2
|
|
|
|
|
8
|
|
|
2
|
|
|
|
|
90
|
|
63
|
2
|
|
|
2
|
|
11
|
use warnings; |
|
2
|
|
|
|
|
15
|
|
|
2
|
|
|
|
|
553
|
|
64
|
2
|
|
|
2
|
|
21
|
use warnings::register; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
319
|
|
65
|
2
|
|
|
2
|
|
12
|
use Carp; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
172
|
|
66
|
2
|
|
|
2
|
|
3602
|
use Lingua::Phonology::Common; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
sub err ($) { _err($_[0]) if warnings::enabled() }; |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
our $VERSION = 0.2; |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
# %valid defines valid feature types |
73
|
|
|
|
|
|
|
my %valid = ( |
74
|
|
|
|
|
|
|
privative => 1, |
75
|
|
|
|
|
|
|
binary => 1, |
76
|
|
|
|
|
|
|
scalar => 1, |
77
|
|
|
|
|
|
|
node => 1 |
78
|
|
|
|
|
|
|
); |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
# Constructor |
81
|
|
|
|
|
|
|
sub new { |
82
|
|
|
|
|
|
|
my $proto = shift; |
83
|
|
|
|
|
|
|
my $class = ref($proto) || $proto; |
84
|
|
|
|
|
|
|
bless {}, $class; |
85
|
|
|
|
|
|
|
} |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
# Add features to our set |
88
|
|
|
|
|
|
|
sub add_feature { |
89
|
|
|
|
|
|
|
my $self = shift; |
90
|
|
|
|
|
|
|
my %hash = @_; |
91
|
|
|
|
|
|
|
my $err = 0; |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
FEATURE: for (keys(%hash)) { |
94
|
|
|
|
|
|
|
unless (_is($hash{$_}, 'HASH')) { |
95
|
|
|
|
|
|
|
err("Bad value for $_"); |
96
|
|
|
|
|
|
|
$err = 1; |
97
|
|
|
|
|
|
|
next FEATURE; |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
# Error checking--these invalidate the whole feature |
101
|
|
|
|
|
|
|
if (not $hash{$_} = _check_featureref($_, $hash{$_})) { |
102
|
|
|
|
|
|
|
$err = 1; |
103
|
|
|
|
|
|
|
next FEATURE; |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
# Drop any old feature |
107
|
|
|
|
|
|
|
$self->drop_feature($_) if $self->feature_exists($_); |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
# Add the new feature |
110
|
|
|
|
|
|
|
$self->_add_featureref($_, $hash{$_}) or $err = 1; |
111
|
|
|
|
|
|
|
} |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
return $err ? () : 1; |
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
# Change an existing feature. Same as add_feature(), but checks that the |
117
|
|
|
|
|
|
|
# feature exists first |
118
|
|
|
|
|
|
|
sub change_feature { |
119
|
|
|
|
|
|
|
my ($self, %hash) = @_; |
120
|
|
|
|
|
|
|
my $err = 0; |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
FEATURE: for (keys(%hash)) { |
123
|
|
|
|
|
|
|
if (not $self->feature($_)) { |
124
|
|
|
|
|
|
|
$err = 1; |
125
|
|
|
|
|
|
|
next FEATURE; |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
if (not _is($hash{$_}, 'HASH')) { |
129
|
|
|
|
|
|
|
err "Bad value for $_"; |
130
|
|
|
|
|
|
|
$err = 1; |
131
|
|
|
|
|
|
|
next FEATURE; |
132
|
|
|
|
|
|
|
} |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
# Check the href |
135
|
|
|
|
|
|
|
if (not $hash{$_} = _check_featureref($_, $hash{$_})) { |
136
|
|
|
|
|
|
|
$err = 1; |
137
|
|
|
|
|
|
|
next FEATURE; |
138
|
|
|
|
|
|
|
} |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
# Add the ref |
141
|
|
|
|
|
|
|
$self->_add_featureref($_, $hash{$_}) or $err = 1; |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
} |
144
|
|
|
|
|
|
|
return $err ? () : 1; |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
# Private -- check a hashref |
148
|
|
|
|
|
|
|
sub _check_featureref { |
149
|
|
|
|
|
|
|
my ($name, $ref) = @_; |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
# Check types |
152
|
|
|
|
|
|
|
$ref->{type} = lc $ref->{type}; |
153
|
|
|
|
|
|
|
if (not $valid{$ref->{type}}) { |
154
|
|
|
|
|
|
|
return err("Invalid feature type '$ref->{type}' for feature $name"); |
155
|
|
|
|
|
|
|
} |
156
|
|
|
|
|
|
|
$ref->{type} = 'privative' if $ref->{type} eq 'node'; |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
# Check children |
159
|
|
|
|
|
|
|
if ($ref->{child} && not _is($ref->{child}, 'ARRAY')) { |
160
|
|
|
|
|
|
|
return err("Bad value for child of $name"); |
161
|
|
|
|
|
|
|
} |
162
|
|
|
|
|
|
|
$ref->{child} ||= []; |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
# Check parents |
165
|
|
|
|
|
|
|
if ($ref->{parent} && not _is($ref->{parent}, 'ARRAY')) { |
166
|
|
|
|
|
|
|
return err("Bad value for parent of $name"); |
167
|
|
|
|
|
|
|
} |
168
|
|
|
|
|
|
|
$ref->{parent} ||= []; |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
# All OK |
171
|
|
|
|
|
|
|
return $ref; |
172
|
|
|
|
|
|
|
} |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
# Private -- apply a hashref |
175
|
|
|
|
|
|
|
sub _add_featureref { |
176
|
|
|
|
|
|
|
my ($self, $name, $ref) = @_; |
177
|
|
|
|
|
|
|
my $err = 0; |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
$self->{$name}->{type} = $ref->{type}; |
180
|
|
|
|
|
|
|
$self->{$name}->{child} = {}; |
181
|
|
|
|
|
|
|
$self->{$name}->{parent} = {}; |
182
|
|
|
|
|
|
|
$self->add_child($name, @{$ref->{child}}) or $err = 1; |
183
|
|
|
|
|
|
|
$self->add_parent($name, @{$ref->{parent}}) or $err = 1; |
184
|
|
|
|
|
|
|
return $err ? () : 1; |
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
# Get a feature or get warned |
189
|
|
|
|
|
|
|
sub feature { |
190
|
|
|
|
|
|
|
my ($self, $feature) = @_; |
191
|
|
|
|
|
|
|
return $self->{$feature} if exists($self->{$feature}); |
192
|
|
|
|
|
|
|
return err("No such feature '$feature'"); |
193
|
|
|
|
|
|
|
} |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
# Check if a feature exists (w/o warnings) |
196
|
|
|
|
|
|
|
sub feature_exists { |
197
|
|
|
|
|
|
|
my $self = shift; |
198
|
|
|
|
|
|
|
exists $self->{$_[0]}; |
199
|
|
|
|
|
|
|
} |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
# Drop a feature |
202
|
|
|
|
|
|
|
sub drop_feature { |
203
|
|
|
|
|
|
|
my $self = shift; |
204
|
|
|
|
|
|
|
my $err = 0; |
205
|
|
|
|
|
|
|
for my $drop (@_) { |
206
|
|
|
|
|
|
|
# Remove references to this feature |
207
|
|
|
|
|
|
|
$self->drop_child($drop, $self->children($drop)) or $err = 1; |
208
|
|
|
|
|
|
|
$self->drop_parent($drop, $self->parents($drop)) or $err = 1; |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
# Remove the feature itself |
211
|
|
|
|
|
|
|
delete $self->{$drop}; |
212
|
|
|
|
|
|
|
} |
213
|
|
|
|
|
|
|
return $err ? () : 1; |
214
|
|
|
|
|
|
|
} |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
sub all_features { |
217
|
|
|
|
|
|
|
return %{$_[0]}; |
218
|
|
|
|
|
|
|
} |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
# Get array of children |
221
|
|
|
|
|
|
|
sub children { |
222
|
|
|
|
|
|
|
my ($self, $feature) = @_; |
223
|
|
|
|
|
|
|
return exists $self->{$feature} ? |
224
|
|
|
|
|
|
|
keys %{$self->{$feature}->{child}} : |
225
|
|
|
|
|
|
|
err "No such feature '$feature'"; |
226
|
|
|
|
|
|
|
} |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
# Add a new child to a parent |
229
|
|
|
|
|
|
|
sub add_child { |
230
|
|
|
|
|
|
|
my ($self, $parent, @children) = @_; |
231
|
|
|
|
|
|
|
my $err = 0; |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
# Check that parent exists |
234
|
|
|
|
|
|
|
$self->feature($parent) or return; |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
CHILD: for my $child (@children) { |
237
|
|
|
|
|
|
|
# Check that the child feature exists |
238
|
|
|
|
|
|
|
if (not $self->feature($child)) { |
239
|
|
|
|
|
|
|
$err = 1; |
240
|
|
|
|
|
|
|
next CHILD; |
241
|
|
|
|
|
|
|
} |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
# Mark relations on parents and children |
244
|
|
|
|
|
|
|
$self->{$parent}->{child}->{$child} = undef; |
245
|
|
|
|
|
|
|
$self->{$child}->{parent}->{$parent} = undef; |
246
|
|
|
|
|
|
|
} |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
return $err ? (): 1; |
249
|
|
|
|
|
|
|
} |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
# Get rid of a child |
252
|
|
|
|
|
|
|
sub drop_child { |
253
|
|
|
|
|
|
|
my ($self, $parent, @children) = @_; |
254
|
|
|
|
|
|
|
my $err = 0; |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
# Check that parent exists |
257
|
|
|
|
|
|
|
$self->feature($parent) or return; |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
CHILD: for my $child (@children) { |
260
|
|
|
|
|
|
|
# Check that the child exists |
261
|
|
|
|
|
|
|
if (not $self->feature($child)) { |
262
|
|
|
|
|
|
|
$err = 1; |
263
|
|
|
|
|
|
|
next CHILD; |
264
|
|
|
|
|
|
|
} |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
# Remove marks |
267
|
|
|
|
|
|
|
delete $self->{$parent}->{child}->{$child}; |
268
|
|
|
|
|
|
|
delete $self->{$child}->{parent}->{$parent}; |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
return $err ? () : 1; |
272
|
|
|
|
|
|
|
} |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
# Get current parents |
275
|
|
|
|
|
|
|
sub parents { |
276
|
|
|
|
|
|
|
my ($self, $feature) = @_; |
277
|
|
|
|
|
|
|
return exists $self->{$feature} ? |
278
|
|
|
|
|
|
|
keys %{$self->{$feature}->{parent}} : |
279
|
|
|
|
|
|
|
err "No such feature '$feature'"; |
280
|
|
|
|
|
|
|
} |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
# Add a parent |
283
|
|
|
|
|
|
|
sub add_parent { |
284
|
|
|
|
|
|
|
my ($self, $child, @parents) = @_; |
285
|
|
|
|
|
|
|
my $err = 0; |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
# Check that the child exists |
288
|
|
|
|
|
|
|
$self->feature($child) or return; |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
# This action is identical to add_child, but with order of arguments switched |
291
|
|
|
|
|
|
|
# So just pass the buck |
292
|
|
|
|
|
|
|
for (@parents) { |
293
|
|
|
|
|
|
|
$self->add_child($_, $child) or $err = 1; |
294
|
|
|
|
|
|
|
} |
295
|
|
|
|
|
|
|
return $err ? () : 1; |
296
|
|
|
|
|
|
|
} |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
# Get rid of a parent |
299
|
|
|
|
|
|
|
sub drop_parent { |
300
|
|
|
|
|
|
|
my ($self, $child, @parents) = @_; |
301
|
|
|
|
|
|
|
my $err = 0; |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
# Child exists? |
304
|
|
|
|
|
|
|
$self->feature($child) or return; |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
# Once again, just pass to drop_child |
307
|
|
|
|
|
|
|
for (@parents) { |
308
|
|
|
|
|
|
|
$self->drop_child($_, $child) or $err = 1; |
309
|
|
|
|
|
|
|
} |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
return $err ? () : 1; |
312
|
|
|
|
|
|
|
} |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
# Get/set feature type |
315
|
|
|
|
|
|
|
sub type { |
316
|
|
|
|
|
|
|
my ($self, $feature, $type) = @_; |
317
|
|
|
|
|
|
|
$self->feature($feature) or return; |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
if ($type) { |
320
|
|
|
|
|
|
|
# Check for valid types |
321
|
|
|
|
|
|
|
return err("Invalid type $type") if (not $valid{$type}); |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
# Otherwise: |
324
|
|
|
|
|
|
|
$self->{$feature}->{type} = $type; |
325
|
|
|
|
|
|
|
} |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
# Return the current type |
328
|
|
|
|
|
|
|
$self->{$feature}->{type}; |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
# Load feature definitions from a file |
332
|
|
|
|
|
|
|
sub loadfile { |
333
|
|
|
|
|
|
|
my ($self, $file) = @_; |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
my $parse; |
336
|
|
|
|
|
|
|
# Load defaults when no file given |
337
|
|
|
|
|
|
|
if (not defined $file) { |
338
|
|
|
|
|
|
|
my $start = tell DATA; |
339
|
|
|
|
|
|
|
my $string = join '', ; |
340
|
|
|
|
|
|
|
eval { $parse = _parse_from_string($string, 'features') }; |
341
|
|
|
|
|
|
|
return err($@) if $@; |
342
|
|
|
|
|
|
|
seek DATA, $start, 0; |
343
|
|
|
|
|
|
|
} |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
# Load an actual file |
346
|
|
|
|
|
|
|
else { |
347
|
|
|
|
|
|
|
eval { $parse = _parse_from_file($file, 'features') }; |
348
|
|
|
|
|
|
|
if (!$parse) { |
349
|
|
|
|
|
|
|
return $self->old_loadfile($file); |
350
|
|
|
|
|
|
|
} |
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
$self->_load_from_struct($parse); |
354
|
|
|
|
|
|
|
} |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
# The parser for the old deprecated format |
357
|
|
|
|
|
|
|
sub old_loadfile { |
358
|
|
|
|
|
|
|
my ($self, $file) = @_; |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
eval { $file = _to_handle($file, '<') }; |
361
|
|
|
|
|
|
|
return err($@) if $@; |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
my %children; |
364
|
|
|
|
|
|
|
while (<$file>) { |
365
|
|
|
|
|
|
|
s/\#.*$//; # Strip comments |
366
|
|
|
|
|
|
|
if (/^\s*(\w+)\t+(\w+)(\t+(.*))?/) { |
367
|
|
|
|
|
|
|
my ($name, $type, $children) = ($1, $2, $4); |
368
|
|
|
|
|
|
|
no warnings 'uninitialized'; |
369
|
|
|
|
|
|
|
$self->add_feature($name => {type => $type}); |
370
|
|
|
|
|
|
|
$children{$name} = [ split /\s+/, $children ] if $children; |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
} |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
# Add kids |
376
|
|
|
|
|
|
|
$self->add_child($_, @{$children{$_}}) for keys %children; |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
close $file; |
379
|
|
|
|
|
|
|
return 1; |
380
|
|
|
|
|
|
|
} |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
# Actually apply a structure to the object. Private, called by loadfile() and |
383
|
|
|
|
|
|
|
# Lingua::Phonology |
384
|
|
|
|
|
|
|
sub _load_from_struct { |
385
|
|
|
|
|
|
|
my ($self, $parse) = @_; |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
# This line is perhaps too clever for its own good |
388
|
|
|
|
|
|
|
my %children = map { $_ => delete($parse->{$_}->{child}) } keys %$parse; |
389
|
|
|
|
|
|
|
my %parents = map { $_ => delete($parse->{$_}->{parent}) } keys %$parse; |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
$self->add_feature(%$parse); |
392
|
|
|
|
|
|
|
$self->add_child($_, keys %{$children{$_}}) for keys %children; |
393
|
|
|
|
|
|
|
$self->add_parent($_, keys %{$parents{$_}}) for keys %parents; |
394
|
|
|
|
|
|
|
1; |
395
|
|
|
|
|
|
|
} |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
# Return an XML representation of yourself |
398
|
|
|
|
|
|
|
sub _to_str { |
399
|
|
|
|
|
|
|
my $self = shift; |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
# Construct an appropriate data structure |
402
|
|
|
|
|
|
|
my $struct = {}; |
403
|
|
|
|
|
|
|
for (keys %$self) { |
404
|
|
|
|
|
|
|
# Only make child attrs, not parent attrs |
405
|
|
|
|
|
|
|
$struct->{$_}->{child} = [ map { { name => $_ } } keys %{$self->{$_}->{child}} ]; |
406
|
|
|
|
|
|
|
$struct->{$_}->{type} = $self->{$_}->{type}; |
407
|
|
|
|
|
|
|
} |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
return eval { _string_from_struct({ features => { feature => $struct } }) }; |
410
|
|
|
|
|
|
|
} |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
# The following coderefs translate arbitrary data into numeric equivalents |
413
|
|
|
|
|
|
|
# respecting common linguistic abbreviations like [+foo, -bar, *baz] |
414
|
|
|
|
|
|
|
my %num_form = ( |
415
|
|
|
|
|
|
|
privative => sub { |
416
|
|
|
|
|
|
|
return 1 if $_[0]; |
417
|
|
|
|
|
|
|
return undef; |
418
|
|
|
|
|
|
|
}, |
419
|
|
|
|
|
|
|
binary =>sub { |
420
|
|
|
|
|
|
|
my $value = shift; |
421
|
|
|
|
|
|
|
# Text values |
422
|
|
|
|
|
|
|
return 0 if ($value eq '-'); |
423
|
|
|
|
|
|
|
return 1 if ($value eq '+'); |
424
|
|
|
|
|
|
|
# Other values |
425
|
|
|
|
|
|
|
return 1 if ($value); |
426
|
|
|
|
|
|
|
return 0; |
427
|
|
|
|
|
|
|
}, |
428
|
|
|
|
|
|
|
scalar => sub { |
429
|
|
|
|
|
|
|
return $_[0]; # Nothing happens to scalars |
430
|
|
|
|
|
|
|
} |
431
|
|
|
|
|
|
|
); |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
# Translate our input (presumably text) into a number |
434
|
|
|
|
|
|
|
sub number_form { |
435
|
|
|
|
|
|
|
my $self = shift; |
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
return err("Not enough arguments to number_form") if (@_ < 2); |
438
|
|
|
|
|
|
|
my ($feature, $value) = @_; |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
my $ref = $self->feature($feature) or return; |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
# undef is always valid |
443
|
|
|
|
|
|
|
# '*' is always a synonym for undef |
444
|
|
|
|
|
|
|
return undef if (not defined($value)); |
445
|
|
|
|
|
|
|
return undef if ($value eq '*'); |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
# Otherwise, pass processing to the appropriate coderef |
448
|
|
|
|
|
|
|
return $num_form{$ref->{type}}->($value); |
449
|
|
|
|
|
|
|
} |
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
# These coderefs take numeric data and return their text equivs (inverse of |
452
|
|
|
|
|
|
|
# %num_form) |
453
|
|
|
|
|
|
|
my %text_form = ( |
454
|
|
|
|
|
|
|
privative => sub { |
455
|
|
|
|
|
|
|
return ''; |
456
|
|
|
|
|
|
|
}, |
457
|
|
|
|
|
|
|
binary => sub { |
458
|
|
|
|
|
|
|
return '+' if shift; |
459
|
|
|
|
|
|
|
return '-'; |
460
|
|
|
|
|
|
|
}, |
461
|
|
|
|
|
|
|
scalar => sub { |
462
|
|
|
|
|
|
|
return shift; |
463
|
|
|
|
|
|
|
}, |
464
|
|
|
|
|
|
|
); |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
sub text_form { |
467
|
|
|
|
|
|
|
my $self = shift; |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
return err("Not enough arguments to text_form") if (@_ < 2); |
470
|
|
|
|
|
|
|
my ($feature, $value) = @_; |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
my $ref = $self->feature($feature) or return; |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
# first mash through number_form |
475
|
|
|
|
|
|
|
$value = $self->number_form($feature, $value); |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
# '*' is always a synonym for undef |
478
|
|
|
|
|
|
|
return '*' if (not defined($value)); |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
return $text_form{$ref->{type}}->($value); |
481
|
|
|
|
|
|
|
} |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
1; |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
=head1 METHODS |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
=head2 new |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
my $features = Lingua::Phonology::Features->new(); |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
This method creates and returns a new Features object. It takes no arguments. |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
=head2 add_feature |
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
Adds a new feature to the current list of features. Accepts a list of |
496
|
|
|
|
|
|
|
arguments of the form "feature_name => { ... }", where the value assigned |
497
|
|
|
|
|
|
|
to each feature name must be a hash reference with one or more of the |
498
|
|
|
|
|
|
|
following keys: |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
=over 4 |
501
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
=item * type |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
The type must be one of 'privative', 'binary', 'scalar', or 'node'. |
505
|
|
|
|
|
|
|
The feature created is of the type specified. This key must be defined for all |
506
|
|
|
|
|
|
|
features. As of version 0.3, the 'node' type is deprecated, and is considered |
507
|
|
|
|
|
|
|
synonymous with 'privative'. |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
=item * child |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
The value for this key is a reference to an array of feature names. |
512
|
|
|
|
|
|
|
The features named will be assigned as the children of the feature being |
513
|
|
|
|
|
|
|
defined. Note that any type of feature may have children, and children may be |
514
|
|
|
|
|
|
|
of any type. (This is new in version 0.3.) |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
=item * parent |
517
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
The inverse of C. The value for this key must be a |
519
|
|
|
|
|
|
|
reference to an array of feature names that are assigned as the parents |
520
|
|
|
|
|
|
|
of the feature being added. |
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
=back |
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
Note that the features named in C or C must already be |
525
|
|
|
|
|
|
|
defined when the new feature is added. Thus, trying to add parents and |
526
|
|
|
|
|
|
|
children as part of the same call to C will almost certainly |
527
|
|
|
|
|
|
|
result in errors. |
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
This method return true on success and false if any error occurred. |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
Example: |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
$features->add_feature( |
534
|
|
|
|
|
|
|
anterior => { type => 'binary' }, |
535
|
|
|
|
|
|
|
distributed => { type => 'binary' } |
536
|
|
|
|
|
|
|
); |
537
|
|
|
|
|
|
|
$features->add_feature( |
538
|
|
|
|
|
|
|
Coronal => { type => 'privative', child => ['anterior', 'distributed']} |
539
|
|
|
|
|
|
|
); |
540
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
Note that if you attempt to add a feature that already exists, the preexisting |
542
|
|
|
|
|
|
|
feature will be dropped before the new feature is added. |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
B: The features C are used by |
545
|
|
|
|
|
|
|
Lingua::Phonology::Syllable. They may be defined as part of a user feature set |
546
|
|
|
|
|
|
|
if you insist, but their original definitions may be overwritten, since |
547
|
|
|
|
|
|
|
Lingua::Phonology::Syllable will forcibly redefine those features when it is |
548
|
|
|
|
|
|
|
used. You have been warned. |
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
=head2 feature |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
my $feature = $features->feature('name'); |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
Given the name of a feature, returns a hash reference showing the current |
555
|
|
|
|
|
|
|
settings for that feature. The hash reference will at least contain the key |
556
|
|
|
|
|
|
|
C, naming the feature type, and may contain the keys C and/or |
557
|
|
|
|
|
|
|
C if the feature has some children or parents. If you ask for a feature |
558
|
|
|
|
|
|
|
that doesn't exist, this method will return undef and emit a warning. |
559
|
|
|
|
|
|
|
|
560
|
|
|
|
|
|
|
=head2 feature_exists |
561
|
|
|
|
|
|
|
|
562
|
|
|
|
|
|
|
my $bool = $features->feature_exists('name'); |
563
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
Given the name of the feature, returns a simple truth value indicating whether |
565
|
|
|
|
|
|
|
or not any such feature with that name currently exists. Unlike C, |
566
|
|
|
|
|
|
|
this method never gives an error, and does not return the feature reference on |
567
|
|
|
|
|
|
|
success. This can be used by programs that want to quickly check for the |
568
|
|
|
|
|
|
|
existence of a feature without printing warnings. |
569
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
=head2 all_features |
571
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
my %features = $features->all_features(); |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
Takes no arguments. Returns a hash with feature names as its keys, and the |
575
|
|
|
|
|
|
|
parameters for those features as its values. The values will be hash references |
576
|
|
|
|
|
|
|
the same as those returned from C; |
577
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
=head2 drop_feature |
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
$features->drop_feature('name'); |
581
|
|
|
|
|
|
|
$features->drop_feature(@names); |
582
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
Given one or more feature names, deletes the given feature(s) from the current |
584
|
|
|
|
|
|
|
list of features. Note that deleting a feature does not cause its children to |
585
|
|
|
|
|
|
|
be deleted--it just causes them to revert to being undominated. This method |
586
|
|
|
|
|
|
|
returns true on success, otherwise false with an error. |
587
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
=head2 change_feature |
589
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
This method works identically to add_feature(), but it first checks to see that |
591
|
|
|
|
|
|
|
the feature being changed already exists. If it doesn't, it will give an error. |
592
|
|
|
|
|
|
|
If there are no errors, the method returns true. |
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
The C method can also be used to change existing features. Using |
595
|
|
|
|
|
|
|
C, however, allows you to modify an existing feature without |
596
|
|
|
|
|
|
|
losing existing settings for that feature. For example, consider the following: |
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
$features->add_feature(foo => { type => 'privative', child => ['bar', 'baz'] }); |
599
|
|
|
|
|
|
|
$features->change_feature(foo => { type => 'scalar' }); |
600
|
|
|
|
|
|
|
# foo is still the parent of bar and baz |
601
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
If C had been used in place of C, C would |
603
|
|
|
|
|
|
|
not be the parent of anything, because the original settings for its children |
604
|
|
|
|
|
|
|
would have been lost. |
605
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
=head2 children |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
my @children = $features->children('name'); |
609
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
Takes one argument, the name of a feature. Returns a list of all of the |
611
|
|
|
|
|
|
|
features that are children of the feature given. |
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
=head2 add_child |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
$features->add_child('parent', 'child'); |
616
|
|
|
|
|
|
|
$features->add_child('parent', @children); |
617
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
Takes two or more arguments. The first argument to this method should be the |
619
|
|
|
|
|
|
|
name of a feature. The remaining arguments are the names of features to be |
620
|
|
|
|
|
|
|
assigned as children to the first feature. If all children are added without |
621
|
|
|
|
|
|
|
errors, this function returns true, otherwise false with a warning. |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
=head2 drop_child |
624
|
|
|
|
|
|
|
|
625
|
|
|
|
|
|
|
$features->drop_child('parent', 'child'); |
626
|
|
|
|
|
|
|
$features->drop_child('parent', @children); |
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
Like add_child, the first argument to this function should be the name of a |
629
|
|
|
|
|
|
|
feature, and the remaining arguments are the names of children of that feature. |
630
|
|
|
|
|
|
|
The child features so named will be deleted from the list of children for that |
631
|
|
|
|
|
|
|
node. This function returns true on success, false w/ a warning on any error. |
632
|
|
|
|
|
|
|
|
633
|
|
|
|
|
|
|
=head2 parents |
634
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
my @parents = $features->parents('name'); |
636
|
|
|
|
|
|
|
|
637
|
|
|
|
|
|
|
Takes one argument, the name of a feature. Returns a list of the current |
638
|
|
|
|
|
|
|
parent features of that feature. |
639
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
=head2 add_parent |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
$features->add_parent('child', 'parent'); |
643
|
|
|
|
|
|
|
$features->add_parent('child', @parents); |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
Takes two or more arguments. The first argument is the name of a feature, and |
646
|
|
|
|
|
|
|
the remaining arguments are the names of features that should be parents of |
647
|
|
|
|
|
|
|
that feature. Returns true if all of the attempted operations succeeded, |
648
|
|
|
|
|
|
|
otherwise returns false. |
649
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
=head2 drop_parent |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
$features->drop_parent('child', 'parent'); |
653
|
|
|
|
|
|
|
$features->drop_parent('child', @parents); |
654
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
Takes two or more arguments. The first is a feature name, and the remaining |
656
|
|
|
|
|
|
|
arguments are the names of features that are currently parents of that feature. |
657
|
|
|
|
|
|
|
Those features will cease to be parents of the first feature. Returns true on |
658
|
|
|
|
|
|
|
success, false on error. |
659
|
|
|
|
|
|
|
|
660
|
|
|
|
|
|
|
=head2 type |
661
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
# Get a feature type |
663
|
|
|
|
|
|
|
$features->type('name'); |
664
|
|
|
|
|
|
|
# Set a feature's type to 'binary', for example |
665
|
|
|
|
|
|
|
$features->type('name', 'binary'); |
666
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
Takes one or two arguments. The first argument must be the name of a |
668
|
|
|
|
|
|
|
feature. If there is only one argument, the type for that feature is |
669
|
|
|
|
|
|
|
return. If there are two arguments, the type is set to the second |
670
|
|
|
|
|
|
|
argument and returned. |
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
=head2 loadfile |
673
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
# Load defaults |
675
|
|
|
|
|
|
|
$features->loadfile(); |
676
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
# Load from a file |
678
|
|
|
|
|
|
|
$features->loadfile('phono.xml'); |
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
Takes one argument, the path and name of a file. Reads the lines of the file |
681
|
|
|
|
|
|
|
and adds all of the features defined therein. The file should be an XML file |
682
|
|
|
|
|
|
|
following the format described in L. Consult |
683
|
|
|
|
|
|
|
that module if you need to write an appropriate file by hand. |
684
|
|
|
|
|
|
|
|
685
|
|
|
|
|
|
|
You can also call this method with no arguments, in which case the default |
686
|
|
|
|
|
|
|
feature set is loaded. The default set is described in L<"THE DEFAULT FEATURE |
687
|
|
|
|
|
|
|
SET">. |
688
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
If this method is unable to parse its input as an XML file, it will then pass |
690
|
|
|
|
|
|
|
the file off to C, where it will attempt to parse the the file |
691
|
|
|
|
|
|
|
according to the old, deprecated file format. If you have an existing script |
692
|
|
|
|
|
|
|
that loads a file in the old file format with C, there's nothing |
693
|
|
|
|
|
|
|
that needs to be done immediately since the file will still be parsed |
694
|
|
|
|
|
|
|
correctly. However, you will get warnings telling you that the format you're |
695
|
|
|
|
|
|
|
using is deprecated. |
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
=head2 old_loadfile |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
$features->old_loadfile('filename'); |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
Loads a file in the old (pre-version 0.2) and currently deprecated file format. |
702
|
|
|
|
|
|
|
This format is described below. |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
Feature definition lines should be in this format: |
705
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
feature_name [1 or more tabs] type [1 or more tabs] children (separated by spaces) |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
You can order your features any way you want in the file. The method will |
709
|
|
|
|
|
|
|
take care of ensuring that parents are defined before their children are |
710
|
|
|
|
|
|
|
added and make sure no conflicts result. |
711
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
Lines beginning with a '#' are assumed to be comments and are skipped. |
713
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
This method does NOT load the default features any more. Only C |
715
|
|
|
|
|
|
|
does that. |
716
|
|
|
|
|
|
|
|
717
|
|
|
|
|
|
|
=head2 number_form |
718
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
my $num = $features->number_form('name', $text); |
720
|
|
|
|
|
|
|
|
721
|
|
|
|
|
|
|
Takes two arguments. The first argument is the name of a feature, and the |
722
|
|
|
|
|
|
|
second is a value to be converted into the appropriate numeric format for that |
723
|
|
|
|
|
|
|
feature. This function is provided for convenience, to allow Lingua::Phonology |
724
|
|
|
|
|
|
|
to convert between common textual linguistic notation and its internal numeric |
725
|
|
|
|
|
|
|
representation. |
726
|
|
|
|
|
|
|
|
727
|
|
|
|
|
|
|
The conversion from input value to numeric value depends on what type of |
728
|
|
|
|
|
|
|
feature the feature given in the first argument is. A few general text |
729
|
|
|
|
|
|
|
conventions are recognized to make text parsing easier and to ensure that |
730
|
|
|
|
|
|
|
number_form and L<"text_form"> can be used as inverses of each other. The |
731
|
|
|
|
|
|
|
conversions are as follows: |
732
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
=over 4 |
734
|
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
=item * privatives |
736
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
The string '*' is recognized as a synonym for C in all circumstances. |
738
|
|
|
|
|
|
|
It always returns C. |
739
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
=item * |
741
|
|
|
|
|
|
|
|
742
|
|
|
|
|
|
|
B features return 1 if given any true true value (other than '*'), |
743
|
|
|
|
|
|
|
otherwise C. |
744
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
=item * |
746
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
B features return 1 in the case of a true value, 0 in case of a |
748
|
|
|
|
|
|
|
defined false value, and otherwise C. The string '+' is a synonym for |
749
|
|
|
|
|
|
|
1, and '-' is a synonym for 0. Thus, the following two lines both return |
750
|
|
|
|
|
|
|
0: |
751
|
|
|
|
|
|
|
|
752
|
|
|
|
|
|
|
print $features->number_form('binary_feature', 0); # prints 0 |
753
|
|
|
|
|
|
|
print $features->number_form('binary_feature', '-'); # prints 0 |
754
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
Note, however, if the feature given is a privative feature, the first returns |
756
|
|
|
|
|
|
|
C and the second returns 1. |
757
|
|
|
|
|
|
|
|
758
|
|
|
|
|
|
|
=item * |
759
|
|
|
|
|
|
|
|
760
|
|
|
|
|
|
|
B features return the value that they're given unchanged (unless |
761
|
|
|
|
|
|
|
that value is '*', which is translated to C). |
762
|
|
|
|
|
|
|
|
763
|
|
|
|
|
|
|
=item * |
764
|
|
|
|
|
|
|
|
765
|
|
|
|
|
|
|
=back |
766
|
|
|
|
|
|
|
|
767
|
|
|
|
|
|
|
|
768
|
|
|
|
|
|
|
=head2 text_form |
769
|
|
|
|
|
|
|
|
770
|
|
|
|
|
|
|
my $text = $features->text_form('name', $number); |
771
|
|
|
|
|
|
|
|
772
|
|
|
|
|
|
|
This function is the inverse of number_form. It takes two arguments, a |
773
|
|
|
|
|
|
|
feature name and a numeric value, and returns a text equivalent for the |
774
|
|
|
|
|
|
|
numeric value given. The exact translation depends on the type of the |
775
|
|
|
|
|
|
|
feature given in the first argument. The translations are: |
776
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
=over 4 |
778
|
|
|
|
|
|
|
|
779
|
|
|
|
|
|
|
=item * |
780
|
|
|
|
|
|
|
|
781
|
|
|
|
|
|
|
Any undefined value or the string '*' returns '*'. |
782
|
|
|
|
|
|
|
|
783
|
|
|
|
|
|
|
=item * |
784
|
|
|
|
|
|
|
|
785
|
|
|
|
|
|
|
B features return '*' for undef or logically false values, otherwise |
786
|
|
|
|
|
|
|
'' (an empty string). |
787
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
=item * |
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
B features return '+' if true, '-' if false or equal to '-', and '*' if |
791
|
|
|
|
|
|
|
undefined. |
792
|
|
|
|
|
|
|
|
793
|
|
|
|
|
|
|
=item * |
794
|
|
|
|
|
|
|
|
795
|
|
|
|
|
|
|
B features return their values unchanged, except if they're undefined, |
796
|
|
|
|
|
|
|
in which case they return '*'. |
797
|
|
|
|
|
|
|
|
798
|
|
|
|
|
|
|
=item * |
799
|
|
|
|
|
|
|
|
800
|
|
|
|
|
|
|
=back |
801
|
|
|
|
|
|
|
|
802
|
|
|
|
|
|
|
=head1 THE DEFAULT FEATURE SET |
803
|
|
|
|
|
|
|
|
804
|
|
|
|
|
|
|
If you call the method C> without any arguments, like this: |
805
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
$features->loadfile |
807
|
|
|
|
|
|
|
|
808
|
|
|
|
|
|
|
then the default feature set is loaded. The default feature set is a |
809
|
|
|
|
|
|
|
feature geometry tree based on Clements and Hume (1995), with some |
810
|
|
|
|
|
|
|
modifications. This set gratuitously mixes privative, binary, and scalar |
811
|
|
|
|
|
|
|
features, and may or may not be actually useful to you. |
812
|
|
|
|
|
|
|
|
813
|
|
|
|
|
|
|
Within this feature set, we use the convention of putting top-level |
814
|
|
|
|
|
|
|
(undominated) nodes in ALL CAPS, putting intermediate nodes in Initial Caps, |
815
|
|
|
|
|
|
|
and putting terminal features in lowercase. The following shows the feature |
816
|
|
|
|
|
|
|
tree created, with the types of each feature in parenthesis: |
817
|
|
|
|
|
|
|
|
818
|
|
|
|
|
|
|
# True features |
819
|
|
|
|
|
|
|
ROOT (privative) |
820
|
|
|
|
|
|
|
| |
821
|
|
|
|
|
|
|
+-sonorant (privative) |
822
|
|
|
|
|
|
|
+-approximant (privative) |
823
|
|
|
|
|
|
|
+-vocoid (privative) |
824
|
|
|
|
|
|
|
+-nasal (privative) |
825
|
|
|
|
|
|
|
+-lateral (privative) |
826
|
|
|
|
|
|
|
+-continuant (binary) |
827
|
|
|
|
|
|
|
+-Laryngeal |
828
|
|
|
|
|
|
|
| | |
829
|
|
|
|
|
|
|
| +-spread (privative) |
830
|
|
|
|
|
|
|
| +-constricted (privative) |
831
|
|
|
|
|
|
|
| +-voice (privative) |
832
|
|
|
|
|
|
|
| +-ATR (binary) |
833
|
|
|
|
|
|
|
| |
834
|
|
|
|
|
|
|
+-Place |
835
|
|
|
|
|
|
|
| |
836
|
|
|
|
|
|
|
+-pharyngeal (privative) |
837
|
|
|
|
|
|
|
+-Oral |
838
|
|
|
|
|
|
|
| |
839
|
|
|
|
|
|
|
+-labial (privative) |
840
|
|
|
|
|
|
|
+-Lingual |
841
|
|
|
|
|
|
|
| | |
842
|
|
|
|
|
|
|
| +-dorsal (privative) |
843
|
|
|
|
|
|
|
| +-Coronal |
844
|
|
|
|
|
|
|
| | |
845
|
|
|
|
|
|
|
| +-anterior (binary) |
846
|
|
|
|
|
|
|
| +-distributed (binary) |
847
|
|
|
|
|
|
|
| |
848
|
|
|
|
|
|
|
+-Vocalic |
849
|
|
|
|
|
|
|
| |
850
|
|
|
|
|
|
|
+-aperture (scalar) |
851
|
|
|
|
|
|
|
+-tense (privative) |
852
|
|
|
|
|
|
|
+-Vplace |
853
|
|
|
|
|
|
|
| |
854
|
|
|
|
|
|
|
+-labial (same as above) |
855
|
|
|
|
|
|
|
+-Lingual (same as above) |
856
|
|
|
|
|
|
|
|
857
|
|
|
|
|
|
|
# Features dealing with syllable structure |
858
|
|
|
|
|
|
|
SYLL (privative) |
859
|
|
|
|
|
|
|
| |
860
|
|
|
|
|
|
|
+-onset (privative) |
861
|
|
|
|
|
|
|
+-Rime (privative) |
862
|
|
|
|
|
|
|
| |
863
|
|
|
|
|
|
|
+-nucleus (privative) |
864
|
|
|
|
|
|
|
+-coda (privative) |
865
|
|
|
|
|
|
|
SON (scalar) |
866
|
|
|
|
|
|
|
|
867
|
|
|
|
|
|
|
This feature set is created from the following XML file, which can be treated |
868
|
|
|
|
|
|
|
as an example for creating your own feature sets. |
869
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
|
871
|
|
|
|
|
|
|
|
872
|
|
|
|
|
|
|
|
873
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
|
875
|
|
|
|
|
|
|
|
876
|
|
|
|
|
|
|
|
877
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
|
879
|
|
|
|
|
|
|
|
880
|
|
|
|
|
|
|
|
881
|
|
|
|
|
|
|
|
882
|
|
|
|
|
|
|
|
883
|
|
|
|
|
|
|
|
884
|
|
|
|
|
|
|
|
885
|
|
|
|
|
|
|
|
886
|
|
|
|
|
|
|
|
887
|
|
|
|
|
|
|
|
888
|
|
|
|
|
|
|
|
889
|
|
|
|
|
|
|
|
890
|
|
|
|
|
|
|
|
891
|
|
|
|
|
|
|
|
892
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
|
894
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
|
896
|
|
|
|
|
|
|
|
897
|
|
|
|
|
|
|
|
898
|
|
|
|
|
|
|
|
899
|
|
|
|
|
|
|
|
900
|
|
|
|
|
|
|
|
901
|
|
|
|
|
|
|
|
902
|
|
|
|
|
|
|
|
903
|
|
|
|
|
|
|
|
904
|
|
|
|
|
|
|
|
905
|
|
|
|
|
|
|
|
906
|
|
|
|
|
|
|
|
907
|
|
|
|
|
|
|
|
908
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
|
910
|
|
|
|
|
|
|
|
911
|
|
|
|
|
|
|
|
912
|
|
|
|
|
|
|
|
913
|
|
|
|
|
|
|
|
914
|
|
|
|
|
|
|
|
915
|
|
|
|
|
|
|
|
916
|
|
|
|
|
|
|
|
917
|
|
|
|
|
|
|
|
918
|
|
|
|
|
|
|
|
919
|
|
|
|
|
|
|
|
920
|
|
|
|
|
|
|
|
921
|
|
|
|
|
|
|
|
922
|
|
|
|
|
|
|
|
923
|
|
|
|
|
|
|
|
924
|
|
|
|
|
|
|
|
925
|
|
|
|
|
|
|
|
926
|
|
|
|
|
|
|
|
927
|
|
|
|
|
|
|
|
928
|
|
|
|
|
|
|
|
929
|
|
|
|
|
|
|
|
930
|
|
|
|
|
|
|
|
931
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
|
933
|
|
|
|
|
|
|
|
934
|
|
|
|
|
|
|
|
935
|
|
|
|
|
|
|
|
936
|
|
|
|
|
|
|
|
937
|
|
|
|
|
|
|
|
938
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
|
940
|
|
|
|
|
|
|
|
941
|
|
|
|
|
|
|
|
942
|
|
|
|
|
|
|
|
943
|
|
|
|
|
|
|
|
944
|
|
|
|
|
|
|
|
945
|
|
|
|
|
|
|
|
946
|
|
|
|
|
|
|
|
947
|
|
|
|
|
|
|
|
948
|
|
|
|
|
|
|
|
949
|
|
|
|
|
|
|
|
950
|
|
|
|
|
|
|
|
951
|
|
|
|
|
|
|
|
952
|
|
|
|
|
|
|
|
953
|
|
|
|
|
|
|
|
954
|
|
|
|
|
|
|
|
955
|
|
|
|
|
|
|
|
956
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
|
958
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
|
960
|
|
|
|
|
|
|
|
961
|
|
|
|
|
|
|
|
962
|
|
|
|
|
|
|
=head1 TO DO |
963
|
|
|
|
|
|
|
|
964
|
|
|
|
|
|
|
Improve the default feature set. As it is, it cannot handle uvulars or |
965
|
|
|
|
|
|
|
pharyngeals, and has some quirks in its relationships that lead to strange |
966
|
|
|
|
|
|
|
results. Though some of this is the fault of phonologists who can't make up |
967
|
|
|
|
|
|
|
their minds about how things are supposed to go together. |
968
|
|
|
|
|
|
|
|
969
|
|
|
|
|
|
|
=head1 SEE ALSO |
970
|
|
|
|
|
|
|
|
971
|
|
|
|
|
|
|
L |
972
|
|
|
|
|
|
|
|
973
|
|
|
|
|
|
|
L |
974
|
|
|
|
|
|
|
|
975
|
|
|
|
|
|
|
=head1 REFERENCES |
976
|
|
|
|
|
|
|
|
977
|
|
|
|
|
|
|
Clements, G.N and E. Hume. "The Internal Organization of Speech Sounds." |
978
|
|
|
|
|
|
|
Handbook of Phonological Theory. Ed. John A. Goldsmith. Cambridge, |
979
|
|
|
|
|
|
|
Massachusetts: Blackwell, 2001. 245-306. |
980
|
|
|
|
|
|
|
|
981
|
|
|
|
|
|
|
This article is a terrific introduction to the concept of feature geometry, |
982
|
|
|
|
|
|
|
and also describes ways to write rules in a feature-geometric system. |
983
|
|
|
|
|
|
|
|
984
|
|
|
|
|
|
|
=head1 AUTHOR |
985
|
|
|
|
|
|
|
|
986
|
|
|
|
|
|
|
Jesse S. Bangs > |
987
|
|
|
|
|
|
|
|
988
|
|
|
|
|
|
|
=head1 LICENSE |
989
|
|
|
|
|
|
|
|
990
|
|
|
|
|
|
|
This module is free software. You can distribute and/or modify it under the |
991
|
|
|
|
|
|
|
same terms as Perl itself. |
992
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
=cut |
994
|
|
|
|
|
|
|
|
995
|
|
|
|
|
|
|
__DATA__ |