#!/usr/bin/perl # # make-wsluarm.pl # WSLUA's Reference Manual Generator # # (c) 2006, Luis E. Garcia Onatnon # # Wireshark - Network traffic analyzer # By Gerald Combs # Copyright 1998 Gerald Combs # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # (-: I don't even think writing this in Lua :-) # ...well I wished you had! # # changed by Hadriel Kaplan to do the following: # - generates pretty XML output, to make debugging it easier # - allows modules (i.e., WSLUA_MODULE) to have detailed descriptions # - two (or more) line breaks in comments result in separate paragraphs # - all '&' are converted into their entity names, except inside urls # - all '<', and '>' are converted into their entity names everywhere # - any word(s) wrapped in one star, e.g., *foo bar*, become italics # - any word(s) wrapped in two stars, e.g., **foo bar**, become commands (is there a 'bold'?) # - any word(s) wrapped in backticks, e.g., `foo bar`, become commands (is there something better?) # - any word(s) wrapped in two backticks, e.g., ``foo bar``, become one backtick # - any "[[url]]" becomes an XML ulink with the url as both the url and text # - any "[[url|text]]" becomes an XML ulink with the url as the url and text as text # - any indent with a single leading star '*' followed by space is a bulleted list item # reducing indent or having an extra linebreak stops the list # - any indent with a leading digits-dot followed by space, i.e. "1. ", is a numbered list item # reducing indent or having an extra linebreak stops the list # - supports meta-tagged info inside comment descriptions as follows: # * a line starting with "@note" or "Note:" becomes an XML note line # * a line starting with "@warning" or "Warning:" becomes an XML warning line # * a line starting with "@version" or "@since" becomes a "Since:" line # * a line starting with "@code" and ending with "@endcode" becomes an # XML programlisting block, with no indenting/parsing within the block # The above '@' commands are based on Doxygen commands # # Changed by Gerald Combs to generate AsciiDoc. # - We might want to convert the epan/wslua/*.c markup to AsciiDoc # - ...or we might want to generate Doxygen output instead. use strict; #use V2P; sub deb { # warn $_[0]; } sub gorolla { # a gorilla stays to a chimp like gorolla stays to chomp # but this one returns the shrugged string. my $s = shift; # remove leading newlines and spaces at beginning $s =~ s/^([\n]|\s)*//ms; # remove trailing newlines and spaces at end $s =~ s/([\n]|\s)*$//s; # escape HTML entities everywhere # bold and italics - but don't change a star followed by space (it's a list item) $s =~ s/(\*\*)([^*]+?)(\*\*)/`$2`/g; # bold=command?? # one backtick is quote/command #$s =~ s/([^`]|^)(`)([^`]+?)(`)/$1$3<\/command>/g; # quote=command?? # two backticks are one (...and don't appear anywhere?) #$s =~ s/(``)([^`]+?)(``)/`$2`/g; # quote=command?? # Convert wiki-style '[[url]]' $s =~ s/(\[\[)([^\]\|]+?)(\]\])/link:\$\$$2\$\$:[$2]/g; # handle '[[url|pretty]]' $s =~ s/(\[\[)(([^\]\|]+?)\|\s*([^\]]+?))(\]\])/link:\$\$$3\$\$:[$4]/g; $s; } # break up descriptions based on newlines and keywords # builds an array of paragraphs and returns the array ref # each entry in the array is a single line for doc source, but not a # whole paragraph - there are ""/"" entries in the # array to make them paragraphs - this way the doc source itself is # also pretty, while the resulting output is of course valid # first arg is the array to build into; second arg is an array # of lines to parse - this way it can be called from multiple # other functions with slightly different needs # this function assumes gorolla was called previously sub parse_desc_common { my @r; # a temp array we fill, then copy into @ret below my @ret = @{ $_[0] }; my @lines = @{ $_[1] }; # the following will unfortunately create empty paragraphs too # (ie, followed by ), so we do this stuff to a temp @r # array and then copy the non-empty ones into the passed-in array @ret if ($#lines >= 0) { # capitalize the first letter of the first line $lines[0] = ucfirst($lines[0]); # for each double newline, break into separate para's for (my $idx=0; $idx <= $#lines; $idx++) { $lines[$idx] =~ s/^(\s*)//; # remove leading whitespace # save number of spaces in case we need to know later my $indent = length($1); # if we find @code then treat it as a blob if ($lines[$idx] =~ /^\@code\b/) { my $line = $lines[$idx]; $line =~ s/\@code/[source,lua]\n----\n/; # if this line didn't have ending token, keep eating paragraphs while (!($line =~ /\@endcode\b/) && $idx <= $#lines) { # also insert back the line separator we ate in earlier split() $line .= $lines[++$idx] . "\n"; } # fix ending token, and also remove trailing whitespace before it $line =~ s/[\s\n]*\@endcode/\n----/; $r[++$#r] = $line . "\n"; } elsif ($lines[$idx] =~ /^\s*$/) { # line is either empty or just whitespace, and we're not in a @code block # so it's the end of a previous paragraph, beginning of new one $r[++$#r] = "\n\n"; } else { # We have a regular line, not in a @code block. # Add it as-is. my $line = $lines[$idx]; # If line starts with "Note:" or "@note", make it an admonition if ($line =~ /^[nN]ote:|^\@note /) { $r[++$#r] = "[NOTE]\n"; $r[++$#r] = "====\n"; $line =~ s/^([nN]ote:\s*|\@note\s*)//; $r[++$#r] = "" . $line . "\n"; # keep eating until we find a blank line or end while (!($lines[++$idx] =~ /^\s*$/) && $idx <= $#lines) { $lines[$idx] =~ s/^(\s*)//; # remove leading whitespace $r[++$#r] = "" . $lines[$idx]. "\n"; } $r[++$#r] = "====\n\n"; # If line starts with "Warning:"" or @warning", make it an admonition } elsif ($line =~ /^[wW]arning:|^\@warning /) { $r[++$#r] = "[WARNING]\n"; $r[++$#r] = "====\n"; $line =~ s/^(wW]arning:\s*|\@warning\s*)//; # keep eating until we find a blank line or end $r[++$#r] = "" . $line . "\n"; while (!($lines[++$idx] =~ /^\s*$/) && $idx <= $#lines) { $lines[$idx] =~ s/^(\s*)//; # remove leading whitespace $r[++$#r] = "" . $lines[$idx] . "\n"; } $r[++$#r] = "====\n"; # if line starts with "@version" or "@since", make it a "Since:" } elsif ($line =~ /^\@version |^\@since /) { $line =~ s/^\@version\s+|^\@since\s+/Since: /; $r[++$#r] = "\n" . $line . "\n\n"; # if line starts with single "*" and space, leave it mostly intact. } elsif ($line =~ /^\*\s/) { $r[++$#r] = "\n"; $r[++$#r] = "" . $line . "\n"; # keep eating until we find a blank line or end while (!($lines[++$idx] =~ /^\s*$/) && $idx <= $#lines) { $lines[$idx] =~ s/^(\s*)//; # count and remove leading whitespace # if this is less indented than before, break out last if length($1) < $indent; $r[++$#r] = "" . $lines[$idx] . "\n"; } $r[++$#r] = "\n\n"; # if line starts with "1." and space, leave it mostly intact. } elsif ($line =~ /^1\.\s/) { $r[++$#r] = "\n"; $r[++$#r] = "" . $line . "\n"; # keep eating until we find a blank line or end while (!($lines[++$idx] =~ /^\s*$/) && $idx <= $#lines) { $lines[$idx] =~ s/^(\s*)//; # count and remove leading whitespace # if this is less indented than before, break out last if length($1) < $indent; $r[++$#r] = "" . $lines[$idx] . "\n"; } $r[++$#r] = "\n\n"; # just a normal line, add it to array } else { # Nested Lua arrays $line =~ s/\[\[(.*)\]\]/\$\$$1\$\$/g; $r[++$#r] = "" . $line . "\n"; } } } $r[++$#r] = "\n\n"; # Now go through @r, and copy into @ret but skip empty lines. # This isn't strictly necessary but makes the AsciiDoc output prettier. for (my $idx=0; $idx <= $#r; $idx++) { if ($r[$idx] =~ /^\s*$/ && $r[$idx+1] =~ /^\s*$/ && $r[$idx+2] =~ /^\s*$/) { $idx++; # for-loop will increment $idx and skip the other one } else { $ret[++$#ret] = $r[$idx]; } } } return \@ret; } # for "normal" description cases - class, function, etc. # but not for modules nor function arguments sub parse_desc { my $s = gorolla(shift); # break description into separate sections my @r = (); # the array we return # split each line into an array my @lines = split(/\n/, $s); return parse_desc_common(\@r, \@lines); } # modules have a "title" and an optional description sub parse_module_desc { my $s = gorolla(shift); # break description into separate sections my @r = (); # the array we return my @lines = split(/\n/, $s); my $line = shift @lines; $r[++$#r] = "=== $line\n\n"; return parse_desc_common(\@r, \@lines); } # function argument descriptions are in a sub parse_function_arg_desc { my $s = gorolla(shift); # break description into separate sections my @r = ( "\n" ); # the array we return my @lines = split(/\n/, $s); @r = @{ parse_desc_common(\@r, \@lines) }; #$r[++$#r] = "\n"; return \@r; } # attributes have a "mode" and an optional description sub parse_attrib_desc { my $s = gorolla(shift); # break description into separate sections my @r = (); # the array we return my $mode = shift; if ($mode) { $mode =~ s/RO/ Retrieve only./; $mode =~ s/WO/ Assign only./; $mode =~ s/RW|WR/ Retrieve or assign./; $r[++$#r] = "Mode: $mode\n\n"; } else { die "Attribute does not have a RO/WO/RW mode: '$s'\n"; } # split each line into an array my @lines = split(/\n/, $s); return parse_desc_common(\@r, \@lines); } # prints the parse_* arrays into the doc source file with pretty indenting # first arg is the description array, second is indent level sub print_desc { my $desc_ref = $_[0]; my $indent = $_[1]; if (!$indent) { $indent = 2; } #my $tabs = "\t" x $indent; for my $line ( @{ $desc_ref } ) { printf D "%s", $line; } } my %module = (); my %modules = (); my $class; my %classes; my $function; my @functions; my $asciidoc_template = { module_header => "[[lua_module_%s]]\n\n", # module_desc => "\t%s\n", class_header => "[[lua_class_%s]]\n\n" . "==== %s\n\n", #class_desc => "\t\t%s\n", class_attr_header => "[[lua_class_attrib_%s]]\n\n" . "===== %s\n\n", #class_attr_descr => "\t\t\t%s%s\n", class_attr_footer => "// End %s\n\n", function_header => "[[lua_fn_%s]]\n\n" . "===== %s\n\n", #function_descr => "\t\t\t%s\n", function_args_header => "[float]\n" . "===== Arguments\n\n", function_arg_header => "%s::\n", #function_arg_descr => "\t\t\t\t\t\t\n" . # "\t\t\t\t\t\t\t%s\n" . # "\t\t\t\t\t\t\n", function_arg_footer => "// function_arg_footer: %s\n\n", function_args_footer => "// end of function_args\n\n", function_argerror_header => "", #"\t\t\t\t\t
Errors\n\t\t\t\t\t\t\n", function_argerror => "", #"\t\t\t\t\t\t\t%s\n", function_argerror_footer => "", #"\t\t\t\t\t\t
\n", function_returns_header => "[float]\n" . "===== Returns\n\n", function_returns => "%s\n\n", function_returns_footer => "// function_returns_footer: %s\n", function_errors_header => "[float]\n" . "===== Errors\n\n", function_errors => "* %s\n", function_errors_footer => "// function_errors_footer: %s\n", function_footer => "// function_footer: %s\n\n", class_footer => "// class_footer: %s\n", global_functions_header => "[[global_functions_%s]]\n\n" . "==== Global Functions\n\n", global_functions_footer => "// Global function\n", module_footer => "// end of module\n", }; # class_constructors_header => "\t\t
\n\t\t\t%s Constructors\n", # class_constructors_footer => "\t\t
\n", # class_methods_header => "\t\t
\n\t\t\t%s Methods\n", # class_methods_footer => "\t\t
\n", my $template_ref = $asciidoc_template; my $out_extension = "asciidoc"; # It's said that only perl can parse perl... my editor isn't perl... # if unencoded this causes my editor's autoindent to bail out so I encoded in octal # XXX: support \" within "" my $QUOTED_RE = "\042\050\133^\042\135*\051\042"; # group 1: whole trailing comment (possibly empty), e.g. " /* foo */" # group 2: any leading whitespace. XXX why is this not removed using (?:...) # group 3: actual comment text, e.g. " foo ". my $TRAILING_COMMENT_RE = '((\s*|[\n\r]*)/\*(.*?)\*/)?'; my $IN_COMMENT_RE = '[\s\r\n]*((.*?)\*/)?'; my @control = ( # This will be scanned in order trying to match the re if it matches # the body will be executed immediately after. [ 'WSLUA_MODULE\s*([A-Z][a-zA-Z0-9]+)' . $IN_COMMENT_RE, sub { $module{name} = $1; $module{descr} = parse_module_desc($3); } ], [ 'WSLUA_CLASS_DEFINE(?:_BASE)?\050\s*([A-Z][a-zA-Z0-9]+).*?\051;' . $TRAILING_COMMENT_RE, sub { deb ">c=$1=$2=$3=$4=$5=$6=$7=\n"; $class = { name => $1, descr=> parse_desc($4), constructors => [], methods => [], attributes => [] }; $classes{$1} = $class; } ], [ 'WSLUA_FUNCTION\s+wslua_([a-z_0-9]+)[^\173]*\173' . $TRAILING_COMMENT_RE, sub { deb ">f=$1=$2=$3=$4=$5=$6=$7=\n"; $function = { returns => [], arglist => [], args => {}, name => $1, descr => parse_desc($4), type => 'standalone' }; push @functions, $function; } ], [ 'WSLUA_CONSTRUCTOR\s+([A-Za-z0-9]+)_([a-z0-9_]+).*?\173' . $TRAILING_COMMENT_RE, sub { deb ">cc=$1=$2=$3=$4=$5=$6=$7=\n"; $function = { returns => [], arglist => [], args => {}, name => "$1.$2", descr => parse_desc($5), type => 'constructor' }; push @{${$class}{constructors}}, $function; } ], [ '_WSLUA_CONSTRUCTOR_\s+([A-Za-z0-9]+)_([a-z0-9_]+)\s*(.*?)\052\057', sub { deb ">cc=$1=$2=$3=$4=$5=$6=$7=\n"; $function = { returns => [], arglist => [], args => {}, name => "$1.$2", descr => parse_desc($3), type => 'constructor' }; push @{${$class}{constructors}}, $function; } ], [ 'WSLUA_METHOD\s+([A-Za-z0-9]+)_([a-z0-9_]+)[^\173]*\173' . $TRAILING_COMMENT_RE, sub { deb ">cm=$1=$2=$3=$4=$5=$6=$7=\n"; my $name = "$1"; $name =~ tr/A-Z/a-z/; $name .= ":$2"; $function = { returns => [], arglist => [], args => {}, name => $name, descr => parse_desc($5), type => 'method' }; push @{${$class}{methods}}, $function; } ], [ 'WSLUA_METAMETHOD\s+([A-Za-z0-9]+)(__[a-z0-9]+)[^\173]*\173' . $TRAILING_COMMENT_RE, sub { deb ">cm=$1=$2=$3=$4=$5=$6=$7=\n"; my $name = "$1"; $name =~ tr/A-Z/a-z/; $name .= ":$2"; my ($c,$d) = ($1,$5); $function = { returns => [], arglist => [], args => {}, name => $name, descr => parse_desc($5), type => 'metamethod' }; push @{${$class}{methods}}, $function; } ], # Splits "WSLUA_OPTARG_ProtoField_int8_NAME /* food */" into # "OPT" (1), "ProtoField_int8" (2), "NAME" (3), ..., ..., " food " (6) # Handles functions like "loadfile(filename)" too. [ '#define WSLUA_(OPT)?ARG_((?:[A-Za-z0-9]+_)?[a-z0-9_]+)_([A-Z0-9_]+)\s+\d+' . $TRAILING_COMMENT_RE, sub { deb ">a=$1=$2=$3=$4=$5=$6=\n"; my $name = $1 eq 'OPT' ? "[$3]" : $3; push @{${$function}{arglist}} , $name; ${${$function}{args}}{$name} = {descr=>parse_function_arg_desc($6),} } ], # same as above, except that there is no macro but a (multi-line) comment. [ '\057\052\s*WSLUA_(OPT)?ARG_((?:[A-Za-z0-9]+_)?[a-z0-9_]+)_([A-Z0-9_]+)\s*(.*?)\052\057', sub { deb ">a=$1=$2=$3=$4\n"; my $name = $1 eq 'OPT' ? "[$3]" : $3; push @{${$function}{arglist}} , $name; ${${$function}{args}}{$name} = {descr=>parse_function_arg_desc($4),} } ], [ '/\052\s+WSLUA_ATTRIBUTE\s+([A-Za-z0-9]+)_([a-z_]+)\s+([A-Z]*)\s*(.*?)\052/', sub { deb ">at=$1=$2=$3=$4=$5=$6=$7=\n"; my $name = "$1"; $name =~ tr/A-Z/a-z/; $name .= ".$2"; push @{${$class}{attributes}}, { name => $name, descr => parse_attrib_desc($4, $3) }; } ], [ '/\052\s+WSLUA_MOREARGS\s+([A-Za-z_]+)\s+(.*?)\052/', sub { deb ">ma=$1=$2=$3=$4=$5=$6=$7=\n"; push @{${$function}{arglist}} , "..."; ${${$function}{args}}{"..."} = {descr=>parse_function_arg_desc($2)} } ], [ 'WSLUA_(FINAL_)?RETURN\050\s*.*?\s*\051\s*;' . $TRAILING_COMMENT_RE, sub { deb ">fr=$1=$2=$3=$4=$5=$6=$7=\n"; push @{${$function}{returns}} , gorolla($4) if $4 ne ''; } ], [ '\057\052\s*_WSLUA_RETURNS_\s*(.*?)\052\057', sub { deb ">fr2=$1=$2=$3=$4=$5=$6=$7=\n"; push @{${$function}{returns}} , gorolla($1) if $1 ne ''; } ], [ 'WSLUA_ERROR\s*\050\s*(([A-Z][A-Za-z]+)_)?([a-z_]+),' . $QUOTED_RE , sub { deb ">e=$1=$2=$3=$4=$5=$6=$7=\n"; my $errors; unless (exists ${$function}{errors}) { $errors = ${$function}{errors} = []; } else { $errors = ${$function}{errors}; } push @{$errors}, gorolla($4); } ], [ 'WSLUA_(OPT)?ARG_ERROR\s*\050\s*(([A-Z][A-Za-z0-9]+)_)?([a-z_]+)\s*,\s*([A-Z0-9]+)\s*,\s*' . $QUOTED_RE, sub { deb ">ae=$1=$2=$3=$4=$5=$6=$7=\n"; my $errors; unless (exists ${${${$function}{args}}{$5}}{errors}) { $errors = ${${${$function}{args}}{$5}}{errors} = []; } else { $errors = ${${${$function}{args}}{$5}}{errors}; } push @{$errors}, gorolla($6); } ], ); my $anymatch = '(^ThIsWiLlNeVeRmAtCh$'; for (@control) { $anymatch .= "|${$_}[0]"; } $anymatch .= ')'; # for each file given in the command line args my $file = shift; my $docfile = 0; while ( $file ) { # continue to next loop if the file is not plain text next unless -f $file; if (!$docfile) { $docfile = $file; $docfile =~ s#.*/##; $docfile =~ s/\.c$/.$out_extension/; } open C, "< $file" or die "Can't open input file $file: $!"; open D, "> wsluarm_src/$docfile" or die "Can't open output file wsluarm_src/$docfile: $!"; my $b = ''; $b .= $_ while (); close C; while ($b =~ /$anymatch/ms ) { my $match = $1; # print "\n-----\n$match\n-----\n"; for (@control) { my ($re,$f) = @{$_}; if ( $match =~ /$re/ms) { &{$f}(); $b =~ s/.*?$re//ms; last; } } } # peek at next file to see if it's continuing this module $file = shift; # make sure we get the next plain text file while ($file and !(-f $file)) { $file = shift; } if ($file) { # we have another file - check it out open C, "< $file" or die "Can't open input file $file: $!"; my $peek_for_continue = ''; $peek_for_continue .= $_ while (); close C; if ($peek_for_continue =~ /WSLUA_CONTINUE_MODULE\s*([A-Z][a-zA-Z0-9]+)/) { if ($module{name} ne $1) { die "Input file $file continues a different module: $1 (previous module is $module{name})!"; } # ok, we're continuing the same module next; } } # if we got here, we're not continuing the module $modules{$module{name}} = $docfile; print "Generating source AsciiDoc for: $module{name}\n"; printf D ${$template_ref}{module_header}, $module{name}, $module{name}; if ($module{descr} && @{$module{descr}} >= 0) { print_desc($module{descr}, 1); } else { die "did NOT print $module{name} description\n"; } for my $cname (sort keys %classes) { my $cl = $classes{$cname}; printf D ${$template_ref}{class_header}, $cname, $cname; if (${$cl}{descr} && @{${$cl}{descr}} >= 0) { print_desc(${$cl}{descr}, 2); } else { die "did NOT print $cname description\n"; } if ( $#{${$cl}{constructors}} >= 0) { for my $c (@{${$cl}{constructors}}) { function_descr($c,3); } } if ( $#{${$cl}{methods}} >= 0) { for my $m (@{${$cl}{methods}}) { function_descr($m, 3); } } if ( $#{${$cl}{attributes}} >= 0) { for my $a (@{${$cl}{attributes}}) { my $a_id = ${$a}{name}; $a_id =~ s/[^a-zA-Z0-9]/_/g; printf D ${$template_ref}{class_attr_header}, $a_id, ${$a}{name}; if (${$a}{descr} && @{${$a}{descr}} >= 0) { print_desc(${$a}{descr}, 3); } else { die "did not print $a_id description\n"; } printf D ${$template_ref}{class_attr_footer}, ${$a}{name}, ${$a}{name}; } } if (exists ${$template_ref}{class_footer}) { printf D ${$template_ref}{class_footer}, $cname, $cname; } } if ($#functions >= 0) { printf D ${$template_ref}{global_functions_header}, $module{name}; for my $f (@functions) { function_descr($f, 3); } print D ${$template_ref}{global_functions_footer}; } printf D ${$template_ref}{module_footer}, $module{name}; close D; %module = (); %classes = (); $class = undef; $function = undef; @functions = (); $docfile = 0; } sub function_descr { my $f = $_[0]; my $indent = $_[1]; my $section_name = 'UNKNOWN'; my $arglist = ''; for (@{ ${$f}{arglist} }) { my $a = $_; $a =~ tr/A-Z/a-z/; $arglist .= "$a, "; } $arglist =~ s/, $//; $section_name = "${$f}{name}($arglist)"; $section_name =~ s/[^a-zA-Z0-9]/_/g; printf D ${$template_ref}{function_header}, $section_name , "${$f}{name}($arglist)"; my @desc = ${$f}{descr}; if ($#desc >= 0) { print_desc(@desc, $indent); } print D ${$template_ref}{function_args_header} if $#{${$f}{arglist}} >= 0; for my $argname (@{${$f}{arglist}}) { my $arg = ${${$f}{args}}{$argname}; $argname =~ tr/A-Z/a-z/; $argname =~ s/\[(.*)\]/$1 (optional)/; printf D ${$template_ref}{function_arg_header}, $argname, $argname; my @desc = ${$arg}{descr}; if ($#desc >= 0) { print_desc(@desc, $indent+2); } if ( $#{${$arg}{errors}} >= 0) { printf D ${$template_ref}{function_argerror_header}, $argname, $argname; printf D ${$template_ref}{function_argerror}, $_, $_ for @{${$arg}{errors}}; printf D ${$template_ref}{function_argerror_footer}, $argname, $argname; } printf D ${$template_ref}{function_arg_footer}, $argname, $argname; } print D ${$template_ref}{function_args_footer} if $#{${$f}{arglist}} >= 0; if ( $#{${$f}{returns}} >= 0) { printf D ${$template_ref}{function_returns_header}, ${$f}{name}; printf D ${$template_ref}{function_returns}, $_ for @{${$f}{returns}}; printf D ${$template_ref}{function_returns_footer}, ${$f}{name}; } if ( $#{${$f}{errors}} >= 0) { my $sname = exists ${$f}{section_name} ? ${$f}{section_name} : ${$f}{name}; printf D ${$template_ref}{function_errors_header}, $sname; printf D ${$template_ref}{function_errors}, $_ for @{${$f}{errors}}; printf D ${$template_ref}{function_errors_footer}, ${$f}{name}; } printf D ${$template_ref}{function_footer}, $section_name; }