3 # This file is part of libDAI - http://www.libdai.org/
5 # libDAI is licensed under the terms of the GNU General Public License version
6 # 2, or (at your option) any later version. libDAI is distributed without any
7 # warranty. See the file COPYING for more details.
9 # Copyright (C) 2009 Frederik Eaton [frederik at ofb dot net]
17 @ARGV == 2 or die "Need 2 arguments";
19 my ($header_file,$source_file) = @ARGV;
22 # Regular expressions for nested brackets (uses perl 5.10 features)
23 my ($nested_angle_brackets) = qr/(<(?:[^<>]++|(?1))*>)/;
24 my ($nested_curly_brackets) = qr/({(?:[^{}]++|(?1))*})/;
25 my ($nested_parentheses) = qr/(\((?:[^()]++|(?1))*\))/;
27 # Patterns to match generated code blocks
28 my ($gen_code_start_pat, $gen_code_end_pat, $props_start_pat) =
29 (qr
(/\
*.*GENERATED CODE
: DO NOT EDIT
.*),
30 qr
(/\*.*END OF GENERATED CODE.*\*/),
32 # Actual delimiters to use for generated code blocks
33 my ($gen_code_start, $gen_code_end) =
34 ("/* {{{ GENERATED CODE: DO NOT EDIT. Created by\n".
35 " $0 $header_file $source_file\n*/\n",
36 "/* }}} END OF GENERATED CODE */\n");
38 # Strings to hold text of files
42 # ----------------------------------------------------------------
45 # remove leading and trailing whitespace
53 # split comments and non-comments, returning 2-element array
54 # of (uncommented part, comments)
55 my $comment_pat = qr
#(/\*[^*]*\*+(?:[^/*][^*]*\*+)*/|//[^\n]*)|("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|.[^/"'\\]*)#;
59 $u =~ s
#$comment_pat#defined $2 ? $2 : ""#gse;
60 $c =~ s
#$comment_pat#defined $1 ? $1 : ""#gse;
64 # Run diff, return output
68 open DIFF
, "diff -u \Q$a\E \Q$b\E |"
69 or die "Couldn't run diff";
80 rename($a, $b) or die "Couldn't rename $a to $b";
85 open OUT
, ">", $f or die "Couldn't open $f for writing";
92 # Step through file, appending lines to buffer
93 # - Lines which match generated code blocks are omitted
94 # - Other lines are passed to the subroutine. They are added to buffer
95 # unless the subroutine returns 1 (e.g. if it has already added them)
96 sub process_file
(&$) {
97 my ($sub, $file) = @_;
99 # step through lines of file, appending each one to buffer
100 open IN
, "<", $file or die "Couldn't open $file for reading";
102 # delete anything between GENERATED CODE etc.
103 if (/$gen_code_end_pat/) {
104 die "Unmatched generated code block end at $file line $.";
105 } elsif (/$gen_code_start_pat/) {
110 if (/$gen_code_end_pat/) {
115 die "Unterminated generated code block at $file line $s"
128 # Parse a PROPERTIES() block
129 sub process_properties
($$$) {
130 my ($file, $start_line, $props_text) = @_;
131 my ($errline)="$file:$start_line";
132 $props_text =~ s/^.*PROPERTIES\s*($nested_parentheses)//s
133 or die "Malformed PROPERTIES, $errline: expected PROPERTIES(args)";
137 my (@args) = split /,/, $args;
138 @args == 2 or die "PROPERTIES needs two arguments at $errline";
139 my ($struct_name, $class) = @args;
141 $props_text =~ m/^\s*($nested_curly_brackets)\s*$/s
142 or die "Malformed PROPERTIES, $errline: expected {} after PROPERTIES";
144 $body =~ s/^{(.*)}$/$1/s; # get rid of curly brackets
145 my (@stmts) = split /;\s*\n/s, $body; # split on ";"
146 my (@typedecls) = ();
148 foreach my $s (@stmts) {
149 my ($s, $cmt) = sepcomment
$s;
151 if($s =~ /^\s*$/s) { # extra ";"
153 } elsif($s =~ /DAI_ENUM|typedef/) {
154 push @typedecls, [stripws
$s, $cmt];
156 $s =~ /^\s*[a-zA-Z_][\w:]+\s*$nested_angle_brackets?/
157 or die "Couldn't match statement $s in PROPERTIES at $errline";
158 my $type = stripws
$&;
159 my $name = stripws
$';
161 if($name =~ /^(.*)=(.*)$/) {
163 $default = stripws $2;
165 push @vars, [$type, $name, $default, $cmt];
173 my $indent = (' 'x12
);
174 for my $d (@typedecls) {
175 my ($decl,$cmt) = @
$d;
177 $text .= join("\n", map { "$indent$_" } split /\n/s, $cmt)."\n";
179 $text .= "$indent$decl;\n"
182 my ($type,$name,$default,$cmt) = @
$v;
184 $text .= join("\n", map { "$indent$_" } split /\n/s, $cmt)."\n"
186 $text .= "$indent$type $name;\n";
191 /// Set members from PropertySet
192 /** \\throw UNKNOWN_PROPERTY_TYPE if a Property key is not recognized
193 * \\throw NOT_ALL_PROPERTIES_SPECIFIED if an expected Property is missing
195 void set(const PropertySet &opts);
196 /// Get members into PropertySet
197 PropertySet get() const;
198 /// Convert to a string which can be parsed as a PropertySet
199 std::string toString() const;
206 void ${class}::Properties::set(const PropertySet &opts)
208 const std::set<PropertyKey> &keys = opts.keys();
209 std::set<PropertyKey>::const_iterator i;
210 for(i=keys.begin(); i!=keys.end(); i++) {
213 my ($type,$name,$default,$cmt) = @
$v;
215 if(*i == "$name") continue;
219 DAI_THROWE(UNKNOWN_PROPERTY_TYPE, "$class: Unknown property " + *i);
223 my ($type,$name,$default,$cmt) = @
$v;
224 if(!defined $default) {
226 if(!opts.hasKey("$name"))
227 DAI_THROWE(NOT_ALL_PROPERTIES_SPECIFIED,"$class: Missing property \\\"$name\\\" for method \\\"\Q$class\E\\\"");
235 my ($type,$name,$default,$cmt) = @
$v;
236 if(defined $default) {
238 if(opts.hasKey("$name")) {
239 $name = opts.getStringAs<$type>("$name");
246 $name = opts.getStringAs<$type>("$name");
253 PropertySet ${class}::Properties::get() const {
258 my ($type,$name,$default,$cmt) = @
$v;
259 # $text .= qq{opts.set("$name", $name);};
261 opts.Set("$name", $name);
268 string ${class}::Properties::toString() const {
269 stringstream s(stringstream::out);
275 my ($type,$name,$default,$cmt) = @
$v;
277 my $c = ($i<@vars)?
" << \",\"":"";
279 s << "$name=" << $name$c;
287 } // end of namespace dai
289 return ($gen_code_start.$text.$gen_code_end, $gen_code_start.$stext.$gen_code_end);
292 # ----------------------------------------------------------------
295 $source_buffer = process_file
{ return 0; } $source_file;
296 $source_buffer =~ s/\n+$//s;
298 $header_buffer = process_file
{
299 if (/$props_start_pat/) {
300 # when we see something resembling properties, record it, and when we
301 # get to the end, process and emit the generated code
308 $buffer .= $props_text;
310 my ($htext, $stext) = process_properties
($header_file, $start_line, $props_text);
312 $source_buffer .= "\n\n\n".$stext;
318 # Write new contents of files to temporary locations
319 my $header_tmp = "$header_file.new";
320 my $source_tmp = "$source_file.new";
321 writefile
($header_buffer, $header_tmp);
322 writefile
($source_buffer, $source_tmp);
324 # Get a diff of changes, show it to the user, and ask for confirmation
326 $diff = getdiff
($header_file, $header_tmp);
327 $diff .= getdiff
($source_file, $source_tmp);
330 warn "No differences\n";
332 my $pager = $ENV{PAGER
} || "less";
333 open PAGER
, "|$pager";
337 print STDERR
"Replace old with new version? (y|N) ";
340 myrename
($header_tmp, $header_file);
341 myrename
($source_tmp, $source_file);