File Coverage

blib/lib/Astro/XSPEC/Model/Parse.pm
Criterion Covered Total %
statement 89 91 97.8
branch 24 38 63.1
condition 5 6 83.3
subroutine 13 13 100.0
pod 2 2 100.0
total 133 150 88.6


line stmt bran cond sub pod time code
1             # --8<--8<--8<--8<--
2             #
3             # Copyright (C) 2010 Smithsonian Astrophysical Observatory
4             #
5             # This file is part of Astro::XSPEC::Model::Parse
6             #
7             # Astro::XSPEC::Model::Parse is free software: you can redistribute it
8             # and/or modify it under the terms of the GNU General Public License
9             # as published by the Free Software Foundation, either version 3 of
10             # the License, or (at your option) any later version.
11             #
12             # This program is distributed in the hope that it will be useful,
13             # but WITHOUT ANY WARRANTY; without even the implied warranty of
14             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15             # GNU General Public License for more details.
16             #
17             # You should have received a copy of the GNU General Public License
18             # along with this program. If not, see .
19             #
20             # -->8-->8-->8-->8--
21              
22 1     1   48989 use strict;
  1         4  
  1         62  
23 1     1   5 use warnings;
  1         2  
  1         106  
24             package Astro::XSPEC::Model::Parse;
25             BEGIN {
26 1     1   18 $Astro::XSPEC::Model::Parse::VERSION = '0.01';
27             }
28              
29 1     1   5 use Carp;
  1         3  
  1         110  
30              
31 1     1   1867 use IO::File;
  1         16180  
  1         151  
32 1     1   984 use Text::ParseWords;
  1         1461  
  1         69  
33 1     1   1115 use Params::Validate qw[ :all ];
  1         12334  
  1         1423  
34              
35             my %model_handler = (
36             start => { type => CODEREF, optional => 1 },
37             end => { type => CODEREF, optional => 1 },
38             );
39              
40             sub new
41             {
42              
43 1     1 1 25 my $class = shift;
44              
45             my %par = validate( @_,
46             {
47             model => { type => HASHREF,
48             callbacks => { 'handler' =>
49             sub {
50 1     1   3 pop @_;
51 1         33 validate( @_, \%model_handler )
52             }
53             },
54 1         80 default => {},
55             },
56             par => { type => CODEREF, optional => 1 },
57             args => { type => HASHREF, optional => 1 },
58             norm => { type => SCALAR, default => 0 }, }
59             );
60              
61 1         17 return bless {@_}, $class;
62             }
63              
64             sub _handle_model {
65              
66 288     288   456 my ( $self, $event, $args ) = @_;
67              
68 288 50       842 return 1 unless defined $self->{model}->{$event};
69              
70 288         306 my $ret = eval { $self->{model}->{$event}->( $event, $args, $self->{args} ) };
  288         1203  
71              
72 288 50       100963 die( "error in model handler for event $event: $@\n" )
73             unless defined $ret;
74              
75 288         787 return $ret;
76             }
77              
78             sub _handle_par {
79              
80 1401     1401   2521 my ( $self, $args ) = @_;
81              
82 1401 50       3566 return 1 unless defined $self->{par};
83              
84 1401         1633 my $ret = eval { $self->{par}->( $args, $self->{args} ) };
  1401         3848  
85              
86 1401 50       11786 die( "error in parameter handler: $@\n" )
87             unless defined $ret;
88              
89 1401         5669 return $ret;
90             }
91              
92             sub parse_file {
93              
94 3     3 1 166827 my ( $self, $file ) = @_;
95              
96 3 50       31 my $fh = IO::File->new( $file )
97             or croak( "$file: error opening file\n" );
98              
99 3         276 my @stanza;
100              
101 3         281 while (my $rec = $fh->getline)
102             {
103 1686         91814 chomp $rec;
104              
105 1686         3806 my $blank = $rec =~ /^\s*$/;
106              
107 1686 100       2743 if ( $blank )
108             {
109             # ignore blank lines between stanzas
110 141 50       369 next if 0 == @stanza;
111             }
112             else
113             {
114 1545         3941 push @stanza, [ $fh->input_line_number, $rec ];
115             }
116              
117             # if we hit a blank line, or this is the last record,
118             # we're done with the current stanza; parse it.
119 1686 100 100     55646 if ( $blank || $fh->eof )
120             {
121 144 50       229 eval { $self->_parse_stanza( \@stanza ) }
  144         405  
122             or croak( "$file: $@\n" );
123              
124 144         4458 @stanza = ();
125             }
126              
127             }
128              
129 3         203 return $self;
130             }
131              
132             sub _parse_stanza {
133              
134 144     144   192 my ( $self, $records ) = @_;
135              
136             # first line is the model spec
137 144         191 my ( $lineno, $record ) = @{ shift @{ $records } };
  144         153  
  144         357  
138 144         822 my @fields = split( ' ', $record );
139              
140 144 50       357 die( "$lineno: syntax error in first line of stanza: $record")
141             unless @fields >= 6;
142              
143 144         188 my %model;
144              
145 144         1037 @model{ qw[ name npars elo ehi subname type calcvar forcecalc ] } = @fields;
146              
147             # delete undefined parameters
148 144         612 delete @model{ grep { ! defined $model{$_} } keys %model };
  1152         2152  
149              
150 144 50       542 $self->_handle_model( 'start', \%model ) or return;
151              
152             # now grab the individual parameters
153 144         186 my @pars;
154 144         555 for my $par ( 1..$model{npars} )
155             {
156 1401         1528 my %par;
157 1401         1480 my $rec = shift @{ $records };
  1401         2334  
158              
159 1401         2577 my ( $lineno, $record ) = @$rec;
160              
161 1401         6596 my @fields = Text::ParseWords::parse_line( qr/\s+/, 0, $record );
162              
163 1401         289423 my $name = shift @fields;
164 1401 100       4222 if ( $name =~ /^\*(.*)/ )
    100          
165             {
166 5         12 $par{type} = 'scale';
167 5         15 $par{name} = $1;
168 5         11 $par{units} = shift @fields;
169 5         9 $par{value} = shift @fields;
170             }
171              
172             elsif( $name =~ /^\$(.*)/ )
173             {
174 13         30 $par{type} = 'switch';
175 13         35 $par{name} = $1;
176 13 100       44 $par{units} = shift @fields
177             if @fields == 7;
178              
179 13         21 $par{value} = shift @fields;
180              
181 13 100       65 @par{ qw( hard_min soft_min soft_max hard_max delta ) } = @fields
182             if @fields == 5;
183             }
184              
185             else
186             {
187 1383         3354 $par{type} = 'variable';
188 1383         2242 $par{name} = $name;
189 1383         7866 @par{ qw( units value hard_min soft_min soft_max hard_max delta periodic ) }
190             = @fields;
191             }
192              
193             # make sure we delete any undefined ones
194 1401         5456 delete @par{ grep { ! defined $par{$_} } keys %par };
  13927         25568  
195              
196 1401         4448 $self->_handle_par( \%par );
197             }
198              
199 144 50       391 if ( @$records )
200             {
201 0         0 die( "$records->[0][0]: extra records in stanza\n" )
202             }
203              
204 144 50 66     673 if ( $model{type} eq 'add' && $self->{norm} )
205             {
206 0 0       0 $self->_handle_par(
207             {
208             type => 'variable',
209             name => 'norm',
210             value => 1,
211             delta => 0.01,
212             hard_min => 0.0,
213             soft_min => 0.0,
214             soft_max => 1e+24,
215             hard_max => 1e+24,
216             }) or return;
217             }
218              
219 144 50       409 $self->_handle_model( 'end', \%model ) or return;
220              
221 144         655 return 1;
222             }
223              
224              
225             1;
226              
227             __END__