File Coverage

blib/lib/Lingua/Phonology/Segment.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2              
3             package Lingua::Phonology::Segment;
4              
5             =head1 NAME
6              
7             Lingua::Phonology::Segment - a module to represent a segment as a bundle
8             of feature values.
9              
10             =head1 SYNOPSIS
11              
12             use Lingua::Phonology;
13             $phono = new Lingua::Phonology;
14              
15             # Define a feature set
16             $features = $phono->features;
17             $features->loadfile;
18              
19             # Make a segment
20             $segment = $phono->segment;
21              
22             # Set some values
23             $segment->labial(1);
24             $segment->continuant(0);
25             $segment->voice(1);
26             # Segment is now voiced labial stop, i.e. [b]
27              
28             # Reset the segment
29             $segment->clear;
30              
31             =head1 DESCRIPTION
32              
33             A Lingua::Phonology::Segment object provides a programmatic representation
34             of a linguistic segment. Such a segment is associated with a
35             Lingua::Phonology::Features object that lists the available features and
36             the relationships between them. The segment itself is a list of the values
37             for those features. This module provides methods for returning and setting
38             these feature values. A segment may also be associated with a
39             Lingua::Phonology::Symbols object, which allows the segment to return the
40             symbol that it best matches.
41              
42             =cut
43              
44 1     1   35204 use strict;
  1         2  
  1         46  
45 1     1   5 use warnings;
  1         3  
  1         37  
46 1     1   5 use warnings::register;
  1         2  
  1         173  
47 1     1   611 use Lingua::Phonology::Common;
  0            
  0            
48             use Lingua::Phonology::Features;
49             use constant {
50             REF => 0,
51             NUM => 1,
52             TXT => 2
53             };
54              
55             # Magical stuff:
56             # Automatically spell segments in string context
57             use overload
58             # The fun stuff
59             '""' => sub { defined $_[0]->{SYMBOLS} ? $_[0]->spell : overload::StrVal($_[0]) },
60             'cmp' => sub {
61             my ($l, $r, $swap) = @_;
62             if ($swap) { return "$r" cmp "$l" }
63             else { return "$l" cmp "$r" } },
64            
65             # A rediculous hack to return the non-overloaded number value. In theory,
66             # '0+' => sub { $_[0] } *should* do this, but it makes the debugger
67             # segfault. This procedure is borrowed from overload.pm itself.
68             '0+' => sub {
69             my $package = ref $_[0];
70             bless $_[0], 'my::Fake';
71             my $rv = int $_[0];
72             bless $_[0], $package;
73             return $rv },
74             'fallback' => 1;
75              
76             our $VERSION = 0.3;
77              
78             sub err ($) { warnings::warnif(shift); return; }
79              
80             # New segment
81             sub new {
82             my $proto = shift;
83             my $class = ref($proto) || $proto;
84             my $self = {
85             FEATURES => undef,
86             SYMBOLS => undef,
87             WANT => REF, # REF, NUM, or TXT
88             VALUES => { }
89             };
90              
91             my $featureset = shift; # An object in class Features
92             my $values = shift; # A hashref
93              
94             # When an object method, copy feature and symbol settings
95             if (ref $proto) {
96             $self->{FEATURES} = $featureset || $proto->{FEATURES};
97             $self->{SYMBOLS} = $proto->{SYMBOLS};
98             }
99              
100             # When a class method
101             else {
102             $self->{FEATURES} = $featureset;
103             }
104              
105             # Require a $featureset of the proper type
106             return err("No featureset (or bad featureset) given for new $class") unless _is_features($self->{FEATURES});
107              
108             # Gesundheit
109             bless $self, $class;
110              
111             # Set initial values
112             $self->value_ref($_, $values->{$_}) for keys %$values;
113              
114             return $self;
115             }
116              
117             sub featureset {
118             my $self = shift;
119             if (@_) {
120             return err("Bad feature set") unless _is_features($_[0]);
121             $self->{FEATURES} = shift;
122             }
123             return $self->{FEATURES};
124             }
125              
126             sub symbolset {
127             my $self = shift;
128             if (@_) {
129             return err("Bad symbol set") unless _is_symbols($_[0]);
130             $self->{SYMBOLS} = shift;
131             }
132             return $self->{SYMBOLS};
133             }
134              
135             # These functions locally set WANT, so that the structure generated in
136             # value_ref() can be built the right way the first time
137              
138             sub value {
139             my $self = shift;
140             local $self->{WANT} = NUM;
141             $self->value_ref(@_);
142             }
143              
144             sub value_text {
145             my $self = shift;
146             local $self->{WANT} = TXT;
147             $self->value_ref(@_);
148             }
149              
150             sub value_ref {
151             my ($self, $feature, $val, $hash) = @_;
152             return unless $self->{FEATURES}->feature($feature);
153              
154             # Assign primary values, checking the size of @_ because $val could be
155             # undef. Skip this part if $val is undef and we have 4 args
156             if (@_ > 2 and not (@_ > 3 and not defined $val)) {
157              
158             # If given a plain scalar ref, replace the old ref
159             if (ref($val) eq 'SCALAR') {
160             # This errs if $$val is not an lvalue
161             eval {
162             $$val = $self->{FEATURES}->number_form($feature, $$val);
163             };
164              
165             # ...in which case we work around
166             if ($@) {
167             my $nval = $self->{FEATURES}->number_form($feature, $$val);
168             $self->{VALUES}->{$feature} = \$nval;
169             }
170             else {
171             $self->{VALUES}->{$feature} = $val;
172             }
173             }
174              
175             # Otherwise, change the val via the current ref
176             else {
177             $val = $self->{FEATURES}->number_form($feature, $val);
178              
179             # If this feature is already defined, assign via existing ref
180             if (my $ref = $self->{VALUES}->{$feature}) {
181             $$ref = $val;
182             }
183            
184             # If it's not defined, assign as a ref
185             else {
186             $self->{VALUES}->{$feature} = \$val;
187             }
188              
189             }
190             }
191              
192             # Assign child values
193             HASH: if (@_ > 3 && defined $hash) {
194             unless (_is($hash, 'HASH')) {
195             err("Third argument to value() must be a hash reference");
196             last HASH;
197             }
198              
199             # Get children that are also in our hash (implicitly ignoring bad children in $hash)
200             for (grep { exists $hash->{$_} } $self->{FEATURES}->children($feature)) {
201             # For pairs like $feature => [ $val, { child vals } ]
202             if (_is($hash->{$_}, 'ARRAY')) {
203             $self->value_ref($_, @{$hash->{$_}});
204             }
205              
206             # For pairs like $feature => $val
207             else {
208             $self->value_ref($_, $hash->{$_});
209             }
210             }
211             }
212              
213             # Quit in void context
214             return if not defined wantarray;
215              
216             # Find the return value or a ref to undef
217             my $retval = $self->{VALUES}->{$feature} || \undef;
218             {
219             # Less strictness here for when $retval is undef
220             #no strict 'refs';
221             #no warnings 'uninitialized';
222              
223             if ($self->{WANT} == TXT) {
224             $retval = $self->{FEATURES}->text_form($feature, $$retval);
225             }
226             elsif ($self->{WANT} == NUM) {
227             $retval = $$retval;
228             }
229             }
230              
231             # Return these in scalar context
232             if (not wantarray) {
233             # Return the actual value if the feature is defined
234             return $retval if exists $self->{VALUES}->{$feature} && defined ${$self->{VALUES}->{$feature}};
235              
236             # Otherwise return the child hashref
237             return $self->_children($feature);
238             }
239              
240             # Get the hashref to return if we want an array
241             my $rethash = $self->_children($feature);
242             return $retval => $rethash; # if (defined $rethash || defined $retval);
243              
244             # When everything's undef
245             return;
246             }
247              
248             # Build a hashref of child values, or undef if you're childless
249             sub _children {
250             my ($self, $feature) = @_;
251             my $rethash = {};
252              
253             # Nodes w/ children
254             if (my @kids = $self->{FEATURES}->children($feature)) {
255             for (@kids) {
256             my ($val, $kids) = $self->value_ref($_);
257             if (defined $val || defined $kids) {
258             $rethash->{$_} = [ $val, $kids ];
259             }
260             }
261             }
262              
263             # Terminal nodes
264             else {
265             return;
266             }
267              
268             # Nodes with no defined children
269             return if not keys %$rethash;
270              
271             # Normal case
272             return $rethash;
273             }
274              
275             sub delink {
276             my $self = shift;
277             my @return = ();
278             for (@_) {
279             push @return, delete($self->{VALUES}->{$_});
280             push @return, $self->delink($self->{FEATURES}->children($_));
281             }
282             return @return;
283             }
284              
285             sub all_values {
286             my $self = shift;
287              
288             # Get the real values for each feature
289             my %h = map { $_ => ${$self->{VALUES}->{$_}} } keys %{$self->{VALUES}};
290             return wantarray ? %h : \%h;
291             }
292              
293             sub spell {
294             my ($self) = @_;
295              
296             return err "No symbol set defined for spell()" if not $self->{SYMBOLS};
297             return $self->{SYMBOLS}->spell($self);
298             }
299              
300             sub duplicate {
301             my $self = shift;
302             return $self->new(undef, { $self->all_values });
303             }
304              
305             sub clear {
306             my $self = shift;
307             $self->{VALUES} = {};
308             return 1;
309             }
310              
311             # Allows you to call changes to feature settings directly
312             # with syntax like $segment->feature_name($value)
313             our $AUTOLOAD;
314             sub AUTOLOAD {
315             my $feature = $AUTOLOAD;
316             $feature =~ s/.*:://;
317             my $self = shift;
318              
319             no strict 'refs';
320             *$feature = sub {
321             my $self = splice @_, 0, 1, $feature;
322             $self->value(@_);
323             };
324              
325             $self->$feature(@_);
326             }
327              
328             sub DESTROY {}
329              
330             1;
331              
332             __END__