#!/usr/bin/env perl use strict; 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 = ""; sub nextToken { my $flags = shift; while ($input =~ /^\s*(#|$)/) { $input = ; if (!defined $input) { return if index ($flags, "e") >= $[; die "unexpected end of file in $ARGV\n"; } } $input =~ s/^\s*($WORD|$STRING|$NOWORD|$SUBST)\s*(#.*\n?)?//; $1 ne $QUOTECHAR or die "unterminated string in $ARGV line $.\n"; #print STDERR "nextToken from $ARGV line $. is $1\n"; $1; } sub peekToken { my $token = nextToken; $input = "$token $input"; $token; } our %subst; sub replace { my $macro = shift; defined $subst{$macro} or die "macro \$($macro) undefined in $ARGV line $.\n"; $subst{$macro}; } 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 "parse error in $ARGV line $.: found '$_' instead of $desc\n"; } s/^$QUOTECHAR(.*)$QUOTECHAR$/$1/; # remove quotes around strings m/\$\(([^)]*)$/ and die "unterminated macro \$($1 in $ARGV line $.\n"; %subst and s/\$\((.*?)\)/replace($1)/ge; # replace macros $_; } sub handleRecord; sub parseRecord { eval { expect "g?record 'record' or 'grecord'" }; # print STDERR "parseRecord $ARGV line $.\n"; if ($@) { $@ =~ /end of file/ or die $@; return 0; } my $rtype = expect "(", "$WORD record type"; my $name = expect ",", "$STRING quoted record name"; expect ")", "{"; my @fields; while (1) { expect "field|} 'field'"; last if $_ eq "}"; push @fields, expect "(", "$WORD field name"; push @fields, expect ",", "$STRING quoted field value"; expect ")"; } handleRecord $rtype,$name,@fields; return 1; } sub parseTemplate { # print STDERR "parseTemplate @_\n"; if (@_) { local *ARGV; local $input; @ARGV = @_; while (parseRecord) {}; } else { while (parseRecord) {}; } return 1; } sub parseSubst { # print STDERR "parseSubst $ARGV line $.\n"; my $filename; eval { $filename = expect "file","$WORD file name" }; if ($@) { $@ =~ /end of file/ or die $@; return 0; } die "template file $filename not found in $ARGV line $.\n" unless -e $filename; die "template file $filename not readable in $ARGV line $.\n" unless -r $filename; eval { expect "{","pattern|{ 'pattern' or '{'" }; if ($_ eq "{") { # old style substitution do { local %subst; while (1) { my $macro; do { $macro = expect "$WORD|,|} macro name"; } while $_ eq ","; # ignore commas last if $_ eq '}'; expect "=", "$STRING|$SUBST value for $macro"; $subst{$macro} = $_; } eval {parseTemplate $filename} or die "$@called from $ARGV line $.\n"; expect "{|} '{' or '}'"; } while $_ eq "{"; } else { # pattern substitution expect "{"; my @macros; while (1) { do { expect "$WORD|,|} macro name"; } while $_ eq ","; # ignore commas last if $_ eq '}'; push @macros, $_; }; while (1) { local %subst; expect "{|} '{' or '}'"; last if $_ eq "}"; foreach my $macro (@macros) { do { expect "$STRING|$SUBST|, value for $macro"; } while $_ eq ","; # ignore commas $subst{$macro} = $_; } expect "}"; eval {parseTemplate $filename} or die "$@called from $ARGV line $.\n"; }; } return 1; } sub parseFiles { foreach (@_) { die "file $_ not found.\n" unless -e $_; die "file $_ not readable.\n" unless -r $_; } foreach (@_) { local *ARGV; local %subst; @ARGV = $_; if (peekToken eq "file") { while (parseSubst) {}; } else { parseTemplate; } } } sub handleRecord { my $rtype = shift; my $rname = shift; print "record($rtype,\"$rname\") {"; while (@_) { my $fieldname = shift; my $value = shift; print "\n\t\tfield($fieldname, \"$value\")"; } print "}\n"; } parseFiles @ARGV