line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#!/usr/bin/perl |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
package Lingua::Phonology::Common; |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
# This module is used for functions needed at least in part by all other |
6
|
|
|
|
|
|
|
# packages. |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
# We export everything. Since this is only for internal use, we know what we're |
9
|
|
|
|
|
|
|
# getting, and the funcs all begin with _, so are unlikely to clash anyway |
10
|
|
|
|
|
|
|
@ISA = qw(Exporter); |
11
|
|
|
|
|
|
|
@EXPORT = qw( |
12
|
|
|
|
|
|
|
_err |
13
|
|
|
|
|
|
|
_is |
14
|
|
|
|
|
|
|
_is_features |
15
|
|
|
|
|
|
|
_is_symbols |
16
|
|
|
|
|
|
|
_is_syllable |
17
|
|
|
|
|
|
|
_is_seg |
18
|
|
|
|
|
|
|
_is_boundary |
19
|
|
|
|
|
|
|
_is_ruleseg |
20
|
|
|
|
|
|
|
_is_tier |
21
|
|
|
|
|
|
|
_to_handle |
22
|
|
|
|
|
|
|
_parse_from_file |
23
|
|
|
|
|
|
|
_parse_from_string |
24
|
|
|
|
|
|
|
_string_from_struct |
25
|
|
|
|
|
|
|
_parse_ext |
26
|
|
|
|
|
|
|
_parse_plain |
27
|
|
|
|
|
|
|
_deparse_ext |
28
|
|
|
|
|
|
|
); |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
$VERSION = 0.1; |
31
|
|
|
|
|
|
|
|
32
|
10
|
|
|
10
|
|
58
|
use strict; |
|
10
|
|
|
|
|
19
|
|
|
10
|
|
|
|
|
429
|
|
33
|
10
|
|
|
10
|
|
54
|
use warnings::register; |
|
10
|
|
|
|
|
19
|
|
|
10
|
|
|
|
|
2120
|
|
34
|
|
|
|
|
|
|
|
35
|
10
|
|
|
10
|
|
185
|
use Carp qw/carp croak/; |
|
10
|
|
|
|
|
24
|
|
|
10
|
|
|
|
|
1024
|
|
36
|
|
|
|
|
|
|
our @CARP_NOT = qw/ |
37
|
|
|
|
|
|
|
Lingua::Phonology |
38
|
|
|
|
|
|
|
Lingua::Phonology::Features |
39
|
|
|
|
|
|
|
Lingua::Phonology::Symbols |
40
|
|
|
|
|
|
|
Lingua::Phonology::Segment |
41
|
|
|
|
|
|
|
Lingua::Phonology::Segment::Rules |
42
|
|
|
|
|
|
|
Lingua::Phonology::Segment::Tier |
43
|
|
|
|
|
|
|
Lingua::Phonology::Segment::Boundary |
44
|
|
|
|
|
|
|
Lingua::Phonology::Rules |
45
|
|
|
|
|
|
|
Lingua::Phonology::Syllable |
46
|
|
|
|
|
|
|
Lingua::Phonology::Word |
47
|
|
|
|
|
|
|
/; |
48
|
10
|
|
|
10
|
|
32379
|
use IO::Handle; |
|
10
|
|
|
|
|
112913
|
|
|
10
|
|
|
|
|
2527
|
|
49
|
10
|
|
|
10
|
|
26652
|
use XML::Simple; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
# Global variables. In principle, modules using this module can change these if |
52
|
|
|
|
|
|
|
# they want, but they probably shouldn't lest evil things transpire. |
53
|
|
|
|
|
|
|
our %xmlin_opts = ( |
54
|
|
|
|
|
|
|
KeyAttr => { feature => 'name', child => 'name', parent => 'name', symbol => 'label' }, |
55
|
|
|
|
|
|
|
ForceArray => [qw/child parent feature symbol rule/], |
56
|
|
|
|
|
|
|
GroupTags => { features => 'feature', symbols => 'symbol', order => 'block', persist => 'rule', block => 'rule' } |
57
|
|
|
|
|
|
|
); |
58
|
|
|
|
|
|
|
our %xmlout_opts = ( |
59
|
|
|
|
|
|
|
KeepRoot => 1, |
60
|
|
|
|
|
|
|
KeyAttr => { feature => 'name', child => 'name', parent => 'name', symbol => 'label', rule => 'name' } |
61
|
|
|
|
|
|
|
); |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
# Concise synonym for UNIVERSAL::isa() with automatic error-writing |
64
|
|
|
|
|
|
|
sub _is($$) { |
65
|
|
|
|
|
|
|
UNIVERSAL::isa(@_); |
66
|
|
|
|
|
|
|
} |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
# Extensions of _is for our own classes |
69
|
|
|
|
|
|
|
sub _is_features ($) { _is(shift, 'Lingua::Phonology::Features') } |
70
|
|
|
|
|
|
|
sub _is_symbols ($) { _is(shift, 'Lingua::Phonology::Symbols') } |
71
|
|
|
|
|
|
|
sub _is_syllable ($) { _is(shift, 'Lingua::Phonology::Syllable') } |
72
|
|
|
|
|
|
|
sub _is_boundary ($) { _is(shift, 'Lingua::Phonology::Segment::Boundary') } |
73
|
|
|
|
|
|
|
sub _is_ruleseg ($) { _is(shift, 'Lingua::Phonology::Segment::Rules') } |
74
|
|
|
|
|
|
|
sub _is_tier ($) { _is(shift, 'Lingua::Phonology::Segment::Tier') } |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
# _is_seg is hacked to allow various segment lookalikes |
77
|
|
|
|
|
|
|
sub _is_seg ($) { |
78
|
|
|
|
|
|
|
my $seg = shift; |
79
|
|
|
|
|
|
|
return _is($seg, 'Lingua::Phonology::Segment') |
80
|
|
|
|
|
|
|
|| _is($seg, 'Lingua::Phonology::Segment::Rules') |
81
|
|
|
|
|
|
|
|| _is($seg, 'Lingua::Phonology::Segment::Tier'); |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
# Make a handle from a filename; don't touch existing handles |
85
|
|
|
|
|
|
|
sub _to_handle($$) { |
86
|
|
|
|
|
|
|
my ($file, $mode) = @_; |
87
|
|
|
|
|
|
|
return $file if _is($file, 'GLOB'); |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
my $handle = IO::Handle->new(); |
90
|
|
|
|
|
|
|
open $handle, $mode, $file or croak "Couldn't open $file: $!"; |
91
|
|
|
|
|
|
|
return $handle; |
92
|
|
|
|
|
|
|
} |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
# Get the parsed XML structure from a filename. Optional second arg specifies |
95
|
|
|
|
|
|
|
# which key of the parse to return. You'd better specify a key that's present |
96
|
|
|
|
|
|
|
# on the topmost level of the parse--this method won't look through the whole |
97
|
|
|
|
|
|
|
# structure for you, like the previous version did. |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
sub _parse_from_file ($;$) { |
100
|
|
|
|
|
|
|
my $file = shift; |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
# Open, slurp, close |
103
|
|
|
|
|
|
|
$file = _to_handle($file, '<') or return; |
104
|
|
|
|
|
|
|
my $string = join '', <$file>; |
105
|
|
|
|
|
|
|
close $file; |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
return _parse_from_string($string, @_); |
108
|
|
|
|
|
|
|
} |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
sub _parse_from_string ($;$) { |
111
|
|
|
|
|
|
|
my ($string, $element) = @_; |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
# Parse the string, check for errors |
114
|
|
|
|
|
|
|
my $parse; |
115
|
|
|
|
|
|
|
eval { $parse = XMLin($string, %xmlin_opts) }; |
116
|
|
|
|
|
|
|
croak "XML parsing error: $@" if ($@); |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
if (defined $element) { |
119
|
|
|
|
|
|
|
return $parse->{$element} if exists $parse->{$element}; |
120
|
|
|
|
|
|
|
croak "<$element> element not found"; |
121
|
|
|
|
|
|
|
} |
122
|
|
|
|
|
|
|
return $parse; |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
# Turn a data structure into an XML string |
126
|
|
|
|
|
|
|
sub _string_from_struct ($) { |
127
|
|
|
|
|
|
|
my $struct = shift; |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
my $string; |
130
|
|
|
|
|
|
|
eval { $string = XMLout($struct, %xmlout_opts) }; |
131
|
|
|
|
|
|
|
croak "Error creating XML: $@" if $@; |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
return $string; |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
sub _parse_ext ($) { |
137
|
|
|
|
|
|
|
my $string = shift; |
138
|
|
|
|
|
|
|
$string =~ s/(-?\d+):/\$_[$1]->/g; |
139
|
|
|
|
|
|
|
return eval "return sub { package main; $string }"; |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
sub _parse_plain ($) { |
143
|
|
|
|
|
|
|
return eval "return sub { package main; $_[0] }"; |
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
sub _deparse_ext ($$) { |
147
|
|
|
|
|
|
|
my ($code, $deparser) = @_; |
148
|
|
|
|
|
|
|
my $string = $deparser->coderef2text($code); |
149
|
|
|
|
|
|
|
$string =~ s/\{(.*)\}/$1/s; # Strip opening/closing brackets |
150
|
|
|
|
|
|
|
#$string =~ s/^\s*(.*?)\s*$/$1/s; # String leading/trailing whitespace |
151
|
|
|
|
|
|
|
$string =~ s/\$_\[(-?\d+)\]->/$1:/gs; # Do ext conversion |
152
|
|
|
|
|
|
|
return $string; |
153
|
|
|
|
|
|
|
} |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
sub _err ($) { |
156
|
|
|
|
|
|
|
carp shift; |
157
|
|
|
|
|
|
|
return; |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
1; |
161
|
|
|
|
|
|
|
|