| 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
|
|
|
|
|
|
|
|