#!/usr/bin/env perl # $Header: /cvs/G/EPICS/App/scripts/dbLoadTemplate,v 1.13 2018/07/05 14:54:40 zimoch Exp $ use strict; use Getopt::Long; use Data::Dumper; my $show_lines; my @include_dirs; GetOptions ("L" => \$show_lines, "I=s" => \@include_dirs, "help|h|?" => \&help) or die("Error in command line arguments\n"); sub help { printf "usage: dbLoadTemplate [options] substitutionfiles\n"; printf "options: -? -h -help : show this help text\n"; printf " -I : add include search dir\n"; printf " -L : output '# file ...' and '# line ...' infos\n"; exit; } my $QUOTECHAR = '"'; my $NOWORDCHAR = ",(){}="; my $NOWORD=qr/[${NOWORDCHAR}${QUOTECHAR}]/; # single non-word character my $WORD=qr/[^\s${NOWORDCHAR}${QUOTECHAR}]+/; # unquoted word my $STRING=qr/(?:$QUOTECHAR((?:\\.|\\\n|.)*?)$QUOTECHAR)/; # quoted string my $SUBST=qr/(\$\(.*?\)|[^\s${NOWORDCHAR}${QUOTECHAR}])+/; # unquoted word with $(macro) allowed # nextToken returns the next input word, string, or char # It skips comments and whitespace # it dies at end of file or on unterminated string our $input = ""; our %subst; our %global; sub replace { my $macro = shift; my $default = shift; if (defined $subst{$macro}) { return $subst{$macro}; } if (defined $default) { #print STDERR "$ARGV:$.:$macro default $default\n"; return $default; } die "$ARGV:$.: macro \$($macro) undefined\n"; } sub expand { #print STDERR "expand $_\n"; m/\$\(([^)]*)$/ and die "$ARGV:$.: unterminated macro \$($1\n"; %subst and s/\$\(([^=)]*)?(=([^)]*))?\)/replace $1,$3/ge; # replace macros $_; } sub nextToken { my $flags = shift; while ($input =~ /^\s*(#|$)/) { $_ = ; if (!defined) { return if index ($flags, "e") >= $[; die "$ARGV:$.: unexpected end of file\n"; } $input = expand; } $input =~ s/^\s*($STRING|$SUBST|$WORD|$NOWORD)\s*(#.*\n?)?//; $1 ne $QUOTECHAR or die "$ARGV:$.: unterminated string\n"; #print STDERR "nextToken from $ARGV:$. is $1\n"; $1; } sub peekToken { my $token; $token = nextToken; $input = "$token $input"; $token; } sub expect { my ($desc, $success, $token); foreach my $want (@_) { $_ = nextToken; if (length $want == 1) { # single character $desc = "'$want'"; $success = $_ eq $want; } else { (my $expr, $desc) = split / /, $want, 2; $desc = "'$expr'" if $desc eq ""; $success = /^$expr$/; } $success or die "$ARGV:$.: parse error: found '$_' instead of $desc\n"; } s/^$QUOTECHAR(.*)$QUOTECHAR$/$1/; # remove quotes around strings expand; } sub findFile { my $filename = shift; # print STDERR "findFile $filename\n"; if (-e $filename) { return $filename; } foreach my $dir (@include_dirs) { my $path = "$dir/$filename"; if (-e $path) { return $path; } } } sub handleRecord; sub handleAlias; sub parseRecord { eval { expect "g?record|alias 'record' or 'grecord' or 'alias'" }; # print STDERR "parseRecord $ARGV:$.\n"; if ($@) { $@ =~ /end of file/ or die $@; return 0; } if ($_ eq "alias") { my $name = expect "(", "$STRING quoted record name"; my $alias = expect ",", "$STRING quoted alias name"; expect ")"; handleAlias $name, $alias, $.; return 1; } my $rtype = expect "(", "$WORD record type"; my $name = expect ",", "$STRING quoted record name"; my $line = $.; expect ")"; eval {expect "{";}; if ($@) { $@ =~ /end of file/ or die $@; handleRecord $rtype,$name,$line; return 0; } my @fields; my @aliases; while (1) { expect "field|alias|info|} 'field or alias or info'"; if ($_ eq "alias") { push @aliases, expect "(", "$STRING quoted alias name"; expect ")"; next; } if ($_ eq "info") { expect "(", "$WORD info name", ",", "$STRING|$SUBST quoted info value", ")"; next; } last if $_ eq "}"; push @fields, expect "(", "$WORD field name"; push @fields, expect ",", "$STRING|$SUBST quoted field value"; push @fields, $.; expect ")"; } handleRecord $rtype,$name,$line,@fields; while (@aliases) { handleAlias $name, shift @aliases; } return 1; } sub parseTemplate { # print STDERR "parseTemplate @_\n"; if (@_) { local *ARGV; local $input; @ARGV = @_; if ($show_lines) { print "# file @_\n";} while (parseRecord) {}; } else { while (parseRecord) {}; } return 1; } sub parseSubst { # print STDERR "parseSubst $ARGV:$.\n"; eval { expect "file|global" }; if ($@) { $@ =~ /end of file/ or die $@; return 0; } if ($_ eq "global") { expect "{"; while (1) { my $macro; $macro = expect "$WORD|,|} macro name"; # one comma allowed if ($_ eq ",") {$macro = expect "$WORD|} macro name";} last if $_ eq '}'; expect "=", "$STRING|$SUBST value for $macro"; $global{$macro} = $_; } return 1; } my $filename = expect "$STRING|$WORD file name"; $filename = (findFile $filename or die "$ARGV:$.: template file $filename not found\n"); eval { expect "{","pattern|{ 'pattern' or '{'" }; if ($_ eq "{") { # old style substitution do { local %subst = %global; while (1) { my $macro; $macro = expect "$WORD|,|} macro name"; # one comma allowed if ($_ eq ",") {$macro = expect "$WORD|} macro name";} last if $_ eq '}'; expect "=", "$STRING|$SUBST value for $macro"; $subst{$macro} = $_; } my $n; $n = scalar (keys %subst); if ($n > 100) { print STDERR "Warning: more than 100 marcos ($n in your case): Don't forget to adjust var dbTemplateMaxVars\n"; } eval {parseTemplate $filename} or die "$@$ARGV:$.: called from here\n"; expect "{|} '{' or '}'"; } while $_ eq "{"; } else { # pattern substitution expect "{"; my @macros; while (1) { expect "$WORD|,|} macro name"; # one comma allowed if ($_ eq ",") {expect "$WORD|} macro name";} last if $_ eq '}'; push @macros, $_; }; my $n; $n = scalar (@macros); if ($n > 100) { print STDERR "Warning: more than 100 marcos ($n in your case): Don't forget to adjust var dbTemplateMaxVars\n"; } while (1) { local %subst = %global; expect "{|} '{' or '}'"; last if $_ eq "}"; foreach my $macro (@macros) { expect "$STRING|$SUBST|, value for $macro"; if ($_ eq ",") {expect "$STRING|$SUBST value for $macro";} $subst{$macro} = $_; } expect "}"; eval {parseTemplate $filename} or die "$@called from $ARGV:$.\n"; }; } return 1; } sub parseFiles { foreach (@_) { local *ARGV; local %global; local %subst; my $filename = findFile $_ or die "file $_ not found\n"; @ARGV = $filename; if (peekToken =~ "file|global") { while (parseSubst) {}; } else { parseTemplate; } } } sub printline { if (!$show_lines) { return; } my $line = shift; our $oldline; if ($line != $oldline+1) { my $d = $line - $oldline - 1; if ($d > 0 && $d < 3) { print substr("\n\n\n",1,$d); } else { print "# line $line\n"; } } $oldline = $line; } sub handleRecord { my $rtype = shift; my $rname = shift; my $line = shift; printline $line; print "record($rtype,\"$rname\") {"; while (@_) { my $fieldname = shift; my $value = shift; my $line = shift; print "\n"; printline $line; print "\t\tfield($fieldname, \"$value\")"; } print "}\n"; } sub handleAlias { my $rname = shift; my $alias = shift; my $line = shift; printline $line; print "alias(\"$rname\",\"$alias\")\n"; } parseFiles @ARGV