File Coverage

blib/lib/Lingua/Zompist/Kebreni.pm
Criterion Covered Total %
statement 197 241 81.7
branch 73 118 61.8
condition 46 80 57.5
subroutine 36 37 97.3
pod 16 23 69.5
total 368 499 73.7


line stmt bran cond sub pod time code
1             package Lingua::Zompist::Kebreni;
2              
3             require 5.005;
4 2     2   47281 use strict;
  2         5  
  2         84  
5             # use warnings;
6             $^W = 1;
7 2     2   11 use Carp;
  2         3  
  2         192  
8              
9             require Exporter;
10              
11 2     2   10 use vars qw($VERSION @ISA @EXPORT_OK @EXPORT %EXPORT_TAGS);
  2         12  
  2         261  
12              
13             $VERSION = '0.90';
14              
15             @ISA = qw(Exporter);
16              
17             use overload '""' => 'to_string',
18 1262     1262   867794 'eq' => sub { $_[0]->to_string eq $_[1] }
19 2     2   3106 ;
  2         2049  
  2         26  
20              
21 2     2   147 use constant BENEFACTIVE => 00001;
  2         4  
  2         196  
22 2     2   10 use constant ANTIBEN => 00002;
  2         3  
  2         88  
23 2     2   10 use constant VOLITIONAL => 00004;
  2         5  
  2         89  
24 2     2   10 use constant PERFECTIVE => 00010;
  2         5  
  2         88  
25 2     2   10 use constant DIR2 => 00020;
  2         4  
  2         80  
26 2     2   24 use constant POLITE => 00040;
  2         4  
  2         76  
27 2     2   8 use constant SUBORDINATE => 00100;
  2         3  
  2         87  
28 2     2   9 use constant SUPPLETIVE => 00200;
  2         3  
  2         81  
29 2     2   8 use constant MADE_POLITE => 00400;
  2         4  
  2         7124  
30              
31             %EXPORT_TAGS = ( 'flags' => [ qw(
32             &BENEFACTIVE
33             &ANTIBEN
34             &VOLITIONAL
35             &PERFECTIVE
36             &DIR3
37             &POLITE
38             &SUBORDINATE
39             &SUPPLETIVE
40             &MADE_POLITE
41             ) ] );
42             $EXPORT_TAGS{'all'} = $EXPORT_TAGS{'flags'};
43              
44             @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
45              
46             @EXPORT = ();
47              
48              
49             # Constructor. This takes a string and returns a Lingua::Zompist::Kebreni
50             # object (which is currently represented internally as an hashref).
51             # Key 'word' is the decomposed form as an arrayref, key 'base' is the
52             # word in Latin script, and key 'suppletive' is 0 or 1, depending on
53             # whether the verb currently stores a suppletive polite form.
54             sub new {
55 56     56 1 4562 my $self = shift;
56 56   66     265 my $class = ref($self) || $self;
57 56         117 my $word;
58             my $base;
59 0         0 my $flags;
60              
61 56 100       244 if(ref $self) {
62 40         100 $flags = $self->flags;
63 40         146 $word = decompose($base = shift);
64 40 50       228 if(@_) { $flags |= shift; }
  40         89  
65 40 50       345 if(@_) { $base = shift; } # override base for e.g. polite suppletives
  40         82  
66             } else {
67 16         41 $word = decompose($base = shift);
68 16   50     60 $flags = shift || 0;
69             }
70              
71 56         1144 return bless {
72             word => $word,
73             base => $base,
74             flags => $flags,
75             }, $class;
76             }
77              
78              
79             # return the base word
80             sub base {
81 5199     5199 1 6837 my $self = shift;
82              
83 5199         35969 return $self->{base};
84             }
85              
86              
87             # return the flags
88             sub flags {
89 3995     3995 1 5466 my $self = shift;
90              
91 3995         10804 return $self->{flags};
92             }
93              
94              
95             # String representation
96             sub to_string {
97 2507     2507 0 600995 my $self = shift;
98              
99             # carp "to_string called";
100              
101             # return $self->recompose;
102              
103 2507         7401 return recompose($self->{word});
104             }
105              
106              
107             # Null operation
108             sub null {
109 17     17 1 1623 return $_[0];
110             }
111              
112              
113              
114              
115             # ========================================================
116             # Verb stuff
117             # Might factor this out into another package some day
118              
119             # Kebreni vowels
120             my %v = map { $_ => 1 } qw(i y u
121             e o
122             a );
123              
124             # Kebreni consonants
125             my %c = map { $_ => 1 } qw(p t c k
126             b d g
127             f th s h' s' h
128             v z z'
129             m n ng
130             l r );
131              
132             # Voiced versions of consonants
133             my %voice = ( 'p' => 'b',
134             't' => 'd',
135             'k' => 'g',
136             's' => 'z',
137             "s'" => "z'",
138             );
139              
140             # Fronted versions of vowels
141             my %front = ( 'a' => 'e',
142             'o' => 'e',
143             'u' => 'y',
144             'y' => 'i',
145             'i' => 'e',
146             'e' => 'e',
147             );
148              
149             # Backed versions of vowels
150             my %back = ( 'a' => 'o',
151             'e' => 'o',
152             'i' => 'y',
153             'y' => 'u',
154             'u' => 'o',
155             'o' => 'o',
156             );
157              
158             # Consonant changes in the subordinating form
159             my %subord= ( 'm' => 'n', # labials become dentals
160             'b' => 't', # b -> d -> t; might as well do it in one step
161             'p' => 't',
162             'd' => 't', # stops become unvoiced
163             'g' => 'k',
164             'z' => 's',
165             "z'" => "s'",
166             );
167              
168             # Raised version of the vowels
169             my %raise = ( 'a' => 'e',
170             'e' => 'i',
171             'o' => 'u',
172             'y' => 'y',
173             'i' => 'i',
174             'u' => 'u',
175             );
176              
177             # Lowered versions of the vowels
178             my %lower = ( 'i' => 'e',
179             'y' => 'e',
180             'e' => 'a',
181             'o' => 'a',
182             'u' => 'o',
183             'a' => 'a',
184             );
185              
186              
187              
188             # decompose a word into phonemes
189             # turns a word into an arrayref; the elements are vowels and consonants
190             # or consonant clusters.
191             # consonant clusters are represented by arrayrefs containing consonants
192             # as elements; other sounds are represented by themselves.
193             # # Returns a Lingua::Zompist::Kebreni::Verb object
194             # sub new ($) {
195             sub decompose {
196             # my $self = shift;
197             # my $class = ref($self) || $self;
198 56     56 0 122 my $word = shift;
199              
200             # split up -- first try two-letter phonemes, then one-letter ones
201 56         572 my @phonemes = $word =~ /(th|s'|z'|h'|ng|[ptckiyubdgfsheovzmnalr])/g;
202              
203             # merge consonant clusters
204 56         101 my $i = 0;
205 56         177 while($i <= $#phonemes) {
206 227 100 66     1403 if($c{$phonemes[$i]} && $i < $#phonemes && $c{$phonemes[$i+1]}) {
      100        
207 4         12 $phonemes[$i] = [ $phonemes[$i] ];
208 4         6 my $j = $i+1;
209 4   66     25 while($j <= $#phonemes && $c{$phonemes[$j]}) {
210 4         5 push @{ $phonemes[$i] }, $phonemes[$j];
  4         12  
211 4         19 $j++;
212             }
213 4         13 splice @phonemes, $i+1, $j-$i-1;
214             }
215 227         669 $i++;
216             }
217              
218             # return bless \@phonemes, $class;
219 56         154 return \@phonemes;
220             }
221              
222              
223             # make a copy of a word
224             sub copy ($) {
225             # the data structure will only ever be two levels deep
226 4122 100   4122 0 4773 return [ map { ref $_ ? [ @$_ ] : $_ } @{$_[0]} ];
  20302         60324  
  4122         8825  
227             }
228              
229              
230             # make a copy of a word
231             sub clone {
232 3915     3915 0 5291 my $self = shift;
233              
234             # return bless copy($self), (ref($self) || $self);
235 3915   33     8875 return bless {
236             word => copy($self->{word}),
237             flags => $self->flags,
238             base => $self->base,
239             }, (ref($self) || $self);
240             }
241              
242              
243             # decompose consonant clusters by adding something in the middle
244             # takes three parameter: the word (an arrayref), the position of the
245             # consonant cluster (e.g. -2), and the something to add (usually a
246             # vowel). This will raise the cluster from an arrayref to a normal array
247             # element status.
248             # However, if the cluster had more than two consonants, only the last
249             # consonant is un-clustered.
250             # # This subroutine should not have to be called from outside the
251             # # Lingua::Zompist::Kebreni::Verb package
252             sub add ($$@) {
253 207     207 0 907 my($word, $position, @newstuff) = @_;
254              
255 207 50       633 return $word unless ref $word->[$position];
256              
257             # Make a copy
258 207         557 $word = copy $word;
259              
260 207 50       814 $position = @$word + $position if $position < 0;
261              
262 207 50       359 if(@{$word->[$position]} == 2) {
  207 0       631  
  0         0  
263 207         909 splice @$word, $position, 1, $word->[$position]->[0], @newstuff, $word->[$position]->[1];
264             } elsif(@{$word->[$position]} > 2) {
265 0         0 my $lastcons = splice @{$word->[$position]}, -1, 1;
  0         0  
266 0         0 splice @$word, $position+1, 0, @newstuff, $lastcons;
267             }
268              
269 207         568 return $word;
270             }
271              
272              
273             # recompose a word out of the arrayref structure
274             sub recompose ($) {
275             # maximum depth is two levels
276 2507 100   2507 0 3266 return join '', map { ref $_ ? @$_ : $_ } @{$_[0]};
  14721         53364  
  2507         6292  
277             }
278              
279              
280             # dump a word from the arrayref form
281             sub dumpword ($) {
282 0 0   0 0 0 return join '', map { ref $_ ? '[' . join('',@$_) . ']' : $_ } @{$_[0]};
  0         0  
  0         0  
283             }
284              
285              
286             # form the perfective of a word. Expect the arrayref form.
287             sub perfective ($) {
288 601     601 1 3454 my $self = shift->clone;
289 601         1616 my $word = $self->{word};
290              
291 601 50       2299 if($self->{flags} & POLITE) {
    50          
292 0         0 croak "Can't make a polite form perfective";
293             } elsif($self->{flags} & SUBORDINATE) {
294 0         0 croak "Can't make a subordinate form perfective";
295             }
296              
297 601 50 33     3376 unless($v{$word->[-1]} && $v{$word->[-3]}) {
298 0         0 croak "Funny word '" . dumpword($word) . "' does not have vowels in positions -3 and -1!";
299             }
300              
301             # swap last two vowels
302 601         2284 ($word->[-1], $word->[-3]) = ($word->[-3], $word->[-1]);
303              
304 601         1178 $self->{flags} |= PERFECTIVE;
305              
306 601         2535 return $self;
307             }
308              
309              
310             # form the volitional of a word. Expect the arrayref form.
311             sub volitional ($) {
312 601     601 1 5183 my $self = shift->clone;
313 601         1822 my $word = $self->{word};
314              
315 601 50       3095 if($self->{flags} & PERFECTIVE) {
    50          
    50          
316 0         0 croak "Can't make a perfective form volitional";
317             } elsif($self->{flags} & POLITE) {
318 0         0 croak "Can't make a polite form volitional";
319             } elsif($self->{flags} & SUBORDINATE) {
320 0         0 croak "Can't make a subordinate form perfective";
321             }
322              
323             # vowel-initial verbs form the volitional as if they had initial 'h'
324             # see http://www.zompist.com/board/messages/94.html
325             # es'u, however, adds 'v' for hysterical raisins
326             # XXX TODO FIXME
327 601 100 100     1109 if($self->base eq "es'u" && !($self->flags & MADE_POLITE)) {
    100          
328 2         6 unshift @$word, 'v';
329             } elsif($v{$word->[0]}) {
330 18         68 unshift @$word, 'h';
331             }
332              
333             # voice the initial consonant
334 601 100       2725 $word->[0] = $voice{$word->[0]} if exists $voice{$word->[0]};
335              
336             # add an inital e
337 601         1860 unshift @$word, 'e';
338              
339             # switch the first two vowels
340 601 50 33     3434 unless($v{$word->[0]} && $v{$word->[2]}) {
341 0         0 croak "Funny word '" . dumpword($word) . "' does not have vowels in positions 0 and 2!";
342             }
343 601         1796 ($word->[0], $word->[2]) = ($word->[2], $word->[0]);
344              
345             # final -y becomes -u
346 601 100       1659 $word->[-1] = 'u' if $word->[-1] eq 'y';
347              
348 601         1220 $self->{flags} |= VOLITIONAL;
349              
350 601         2565 return $self;
351             }
352              
353              
354             # Exceptions with separate polite stems
355             # XXX FIXME TODO
356             my %polite = ( 'badu' => "seh'epu",
357             'tasu' => 'soru',
358             "es'u" => 'natu',
359             );
360              
361              
362             # form the polite stem
363             sub make_polite {
364 602     602 1 46331 my $self = shift;
365              
366 602         1324 $self->{flags} |= MADE_POLITE;
367              
368 602 100       1940 if($polite{$self->base}) {
369 40         128 return $self->new($polite{$self->base}, SUPPLETIVE, $self->base);
370             } else {
371 562         3006 return $self;
372             }
373             }
374              
375              
376             # form the polite form of a word.
377             sub polite ($$) {
378 601     601 1 1215 my $self = shift->clone;
379 601         1562 my $word = $self->{word};
380              
381 601 50       1658 if($self->{flags} & SUBORDINATE) {
382 0         0 croak "Can't make a subordinate form polite";
383             }
384 601 50       1400 unless($self->{flags} & MADE_POLITE) {
385 0         0 croak "Verb must be prepared with ->make_polite before calling ->polite";
386             }
387              
388 601         1281 $self->{flags} |= POLITE;
389              
390             # If the form is a suppletive, don't insert -ri-/-ry-.
391 601 100       1843 return $self if $self->{flags} & SUPPLETIVE;
392              
393             # insert -ri- before the last consonant or -ry- if the vowel in the next
394             # syllable is a _u_.
395 561 100       2219 my @insert = ($word->[-1] eq 'u' ? ('r', 'y') : ('r', 'i'));
396              
397             # check for -VCV
398 561 50 33     2801 unless($v{$word->[-1]} && $v{$word->[-3]}) {
399 0         0 croak "Funny word '" . dumpword($word) . "' does not have vowels in positions -3 and -1!";
400             }
401 561 50 66     2990 unless(ref $word->[-2] or $c{$word->[-2]}) {
402 0         0 croak "Funny word '" . dumpword($word) . "' does not have a consonant in position -2!";
403             }
404              
405             # is the last consonant a cluster? if so, use add(), otherwise just
406             # splice right in
407 561 100       1048 if(ref $word->[-2]) {
408 72         203 $word = add $word, -2, @insert;
409 72         157 $self->{word} = $word;
410             } else {
411 489         1701 splice @$word, -2, 0, @insert;
412             }
413              
414 561         4158 return $self;
415             }
416              
417              
418             # form the benefactive of a word
419             sub benefactive ($) {
420 483     483 1 17421 my $self = shift->clone;
421 483         1469 my $word = $self->{word};
422              
423 483 50       3556 if($self->{flags} & ANTIBEN) {
    50          
    50          
    50          
    50          
424 0         0 croak "Can't make an antibenefactive from benefactive";
425             } elsif($self->{flags} & VOLITIONAL) {
426 0         0 croak "Can't make a volitional form benefactive";
427             } elsif($self->{flags} & PERFECTIVE) {
428 0         0 croak "Can't make a perfective form benefactive";
429             } elsif($self->{flags} & POLITE) {
430 0         0 croak "Can't make a polite form benefactive";
431             } elsif($self->{flags} & SUBORDINATE) {
432 0         0 croak "Can't make a subordinate form benefactive";
433             }
434              
435             # check for -VC[uy]
436 483 50       1286 unless($v{$word->[-1]}) {
437 0         0 croak "Funny word '" . dumpword($word) . "' does not have a vowel in position -3!";
438             }
439 483 50 66     2558 unless(ref $word->[-2] or $c{$word->[-2]}) {
440 0         0 croak "Funny word '" . dumpword($word) . "' does not have a consonant in position -2!";
441             }
442 483 50 66     1651 unless($word->[-1] eq 'u' or $word->[-1] eq 'y') {
443 0         0 croak "Funny word '" . dumpword($word) . "' does not end in -u or -y!";
444             }
445              
446             # front the stem vowel
447             # that's the last vowel of the root, according to
448             # http://www.zompist.com/board/messages/94.html
449 483   50     1706 $word->[-3] = $front{$word->[-3]} || '???';
450              
451             # Change the final -u (or -y!) to -i
452 483         666 $word->[-1] = 'i';
453              
454 483         900 $self->{flags} |= BENEFACTIVE;
455              
456 483         2005 return $self;
457             }
458              
459              
460             # form the "(anti)benefactive for the listener"
461             # assumes that benefactive() or antiben() has already been applied
462             sub dir2 ($) {
463 482     482 1 1094 my $self = shift->clone;
464              
465 482 50 66     3265 unless($self->{flags} & BENEFACTIVE or $self->{flags} & ANTIBEN) {
466 0         0 croak "Can apply -to-listener only to (anti)benefactive forms";
467             }
468              
469 482         744 my $word = $self->{word};
470              
471             # check for -VCV
472 482 50 33     2781 unless($v{$word->[-1]} && $v{$word->[-3]}) {
473 0         0 croak "Funny word '" . dumpword($word) . "' does not have vowels in positions -3 and -1!";
474             }
475 482 50 66     2536 unless(ref $word->[-2] or $c{$word->[-2]}) {
476 0         0 croak "Funny word '" . dumpword($word) . "' does not have a consonant in position -2!";
477             }
478              
479             # add -ni- before the final consonant of the root
480              
481             # is the last consonant a cluster? if so, use add(), otherwise just
482             # splice right in
483 482 100       1057 if(ref $word->[-2]) {
484 96         277 $word = add $word, -2, 'n', 'i';
485 96         225 $self->{word} = $word;
486             } else {
487 386         1295 splice @$word, -2, 0, 'n', 'i';
488             }
489              
490 482         1013 $self->{flags} |= DIR2;
491              
492 482         2229 return $self;
493             }
494              
495              
496             # form the antibenefactive
497             sub antiben ($) {
498 482     482 1 19212 my $self = shift->clone;
499 482         5251 my $word = $self->{word};
500              
501 482 50       3785 if($self->{flags} & BENEFACTIVE) {
    50          
    50          
    50          
    50          
502 0         0 croak "Can't make a benefactive from antibenefactive";
503             } elsif($self->{flags} & VOLITIONAL) {
504 0         0 croak "Can't make a volitional form antibenefactive";
505             } elsif($self->{flags} & PERFECTIVE) {
506 0         0 croak "Can't make a perfective form antibenefactive";
507             } elsif($self->{flags} & POLITE) {
508 0         0 croak "Can't make a polite form antibenefactive";
509             } elsif($self->{flags} & SUBORDINATE) {
510 0         0 croak "Can't make a subordinate form antibenefactive";
511             }
512              
513             # check for -VC[uy]
514 482 50       1317 unless($v{$word->[-1]}) {
515 0         0 croak "Funny word '" . dumpword($word) . "' does not have a vowel in position -3!";
516             }
517 482 50 66     2736 unless(ref $word->[-2] or $c{$word->[-2]}) {
518 0         0 croak "Funny word '" . dumpword($word) . "' does not have a consonant in position -2!";
519             }
520 482 50 66     1679 unless($word->[-1] eq 'u' or $word->[-1] eq 'y') {
521 0         0 croak "Funny word '" . dumpword($word) . "' does not end in -u or -y!";
522             }
523              
524             # back the stem vowel
525             # that's the last vowel of the root, according to
526             # http://www.zompist.com/board/messages/94.html
527 482   50     1615 $word->[-3] = $back{$word->[-3]} || '???';
528              
529             # Change the final -u (or -y!) to -a
530 482         937 $word->[-1] = 'a';
531              
532 482         779 $self->{flags} |= ANTIBEN;
533              
534 482         1822 return $self;
535             }
536              
537              
538             # form the subordinating form
539             sub subordinate ($) {
540 601     601 1 2565 my $self = shift->clone;
541 601         1973 my $word = $self->{word};
542              
543             # check for -VCV
544 601 50 33     3435 unless($v{$word->[-1]} && $v{$word->[-3]}) {
545 0         0 croak "Funny word '" . dumpword($word) . "' does not have vowels in positions -3 and -1!";
546             }
547 601 50 66     2906 unless(ref $word->[-2] or $c{$word->[-2]}) {
548 0         0 croak "Funny word '" . dumpword($word) . "' does not have a consonant in position -2!";
549             }
550              
551             # move the final vowel of the verb before the consonant
552              
553             # is the last consonant a cluster? if so, use add(), otherwise just
554             # splice right in
555             # use -1 rather than -2 for the position since we'll have spliced off
556             # the vowel in position -1 already
557 601 100       1703 if(ref $word->[-2]) {
558 36         112 $word = add $word, -1, splice(@$word, -1, 1);
559 36         105 $self->{word} = $word;
560             } else {
561 565         1510 splice @$word, -1, 0, splice(@$word, -1, 1);
562             }
563              
564             # a labial stop becomes dental, a voiced stop becomes unvoiced
565 601   66     3358 $word->[-1] = $subord{$word->[-1]} || $word->[-1];
566              
567             # Add -te
568 601         1322 push @$word, 't', 'e';
569              
570 601         903 $self->{flags} |= SUBORDINATE;
571              
572 601         5725 return $self;
573             }
574              
575              
576             # make the "one who does" form
577             sub whodoes ($) {
578 16     16 1 1108 my $self = shift->clone;
579 16         47 my $word = $self->{word};
580              
581 16         51 splice @$word, -1, 1, 'e', 'u';
582              
583 16         100 return $self;
584             }
585              
586              
587             # make the feminine "one who does" form
588             sub whodoesf ($) {
589 16     16 1 1180 my $self = shift->clone;
590 16         46 my $word = $self->{word};
591              
592 16         48 splice @$word, -1, 1, 'e', 'c';
593              
594 16         112 return $self;
595             }
596              
597              
598             # make the "participle"
599             sub participle ($) {
600 16     16 1 1451 my $self = shift->clone;
601 16         45 my $word = $self->{word};
602              
603             # check for -VCV
604 16 50 33     108 unless($v{$word->[-1]} && $v{$word->[-3]}) {
605 0         0 croak "Funny word '" . dumpword($word) . "' does not have vowels in positions -3 and -1!";
606             }
607 16 50 66     111 unless(ref $word->[-2] or $c{$word->[-2]}) {
608 0         0 croak "Funny word '" . dumpword($word) . "' does not have a consonant in position -2!";
609             }
610              
611             # change final vowel to -a
612 16         31 $word->[-1] = 'a';
613              
614             # is the last consonant a cluster? if so, use add(), otherwise just
615             # splice right in
616 16 100       37 if(ref $word->[-2]) {
617 3         10 $word = add $word, -2, 'i';
618 3         7 $self->{word} = $word;
619             } else {
620 13 100       52 splice @$word, -2, 0, ($word->[-3] eq 'i' ? 'e' : 'i');
621             }
622              
623 16         284 return $self;
624             }
625              
626              
627             # form the action corresponding to a verb
628             sub action ($) {
629 16     16 1 1157 my $self = shift->clone;
630 16         42 my $word = $self->{word};
631              
632             # check for -VCV
633 16 50 33     102 unless($v{$word->[-1]} && $v{$word->[-3]}) {
634 0         0 croak "Funny word '" . dumpword($word) . "' does not have vowels in positions -3 and -1!";
635             }
636 16 50 66     77 unless(ref $word->[-2] or $c{$word->[-2]}) {
637 0         0 croak "Funny word '" . dumpword($word) . "' does not have a consonant in position -2!";
638             }
639              
640             # lower the last root vowel
641 16   50     50 $word->[-3] = $lower{$word->[-3]} || '???';
642              
643             # add -i
644 16         25 $word->[-1] = 'i';
645              
646 16         118 return $self;
647             }
648              
649              
650             1;
651              
652             __END__