Timothe Litt 66e00b9900 Backlog of work since 2016
Too much to list all, but includes (in no particular order):
 - Cleanup for 64-bit builds, MSVC warnings.
 - Structured help
 - Help file compiler.
 - Supports volsets, writes/create work.
 - Support for I18n in messages, help.
 - Makefiles.
 - Initialize volume/volset
 - Command line editing/history

Builds and works on Linux and Windows (VS).
Not recently built or tested on other platforms, but
not intentinonally broken.
2022-10-10 11:00:20 -04:00

1189 lines
39 KiB
Perl
Executable File

#!/usr/bin/perl
# T. Litt March 2016
# Copyright (C) 2016 Timothe Litt
#
# Input from unmessage of VMS VAX message files, generated by
# ftp://ftp.decuserve.org/decus/vms/sig_tapes/vax88b1/meadows/unmessage/unmessage.exe
#
# Because these don't include the message severity (well, they
# do, but they're all 0 ("warning"), we also take a concatenated
# .h file of all the random definition files we can find.
# These are parsed for #defines that look interesting, and those
# are used to correct the severity code.
# The .h file must come first to have any effect.
#
# message vms_msgdefs.h ods2.msg vms_msgdump.msg ssdef.h
#
# Also include the message file for ODS2 itself, as that simplifes
# generating its message catalog.
#
# Currently, the useful VMS messages have been reconstructed into
# vms_messages.msg (by this utility); that file + ods2.msg are the
# only files necessary to rebuild the english language version of
# ODS2. The long procedure (documented in the output file) is only
# useful if you need to refresh the VMS-sourced messages, e.g.
# for an I18n translation.
our $VERSION = 0.1;
use warnings;
use strict;
use File::Copy qw(move);
use Getopt::Std;
$Getopt::Std::STANDARD_HELP_VERSION = 1;
# Facilities of interest
my %wanted = map { $_ => 1} (qw/DISM$ INIT$ MOUNT$ RMS$ SHR$ SS$ SYSTEM$/,
qw/COPY CREATE DELETE DIFF DIRECT DISM EXTEND/,
qw/HELP INIT IO MOUNT ODS2 RENAME SEARCH SHR TYPE/);
# Symbols NOT of interest
# These are known to have no text message for various reasons.
my %ign = map { $_ => 1 } (
qw/SS$_CONTINUE SS$_WASCLR SS$_WASSET/, # Overlaid codes
qw/SS$_NOPRIVSTRT SS$_NOPRIVEND SS$_EXQUOTASTRT SS$_EXQUOTAEND/,
qw/SS$_SYSAPMAX SS$_SYSAPMIN/ # Range markers
);
our( $opt_b, $opt_h, $opt_m, $opt_q, $opt_t );
my $argv = join( ' ', @ARGV );
getopts( 'bhqmt' );
if( $opt_h ) {
print( << 'HELP' );
message [-hmq] in out
in and out default to "-".
in is any mixture of .h and .msg files.
out will be a .h or .msg, the old version will be .bak or .bak.n.
CAUTION: if more than one file is specified, the last file will be
over-written. Use -b.
options:
-b backup output file to .bak (or .bak.n)
-h this message
-m generate a reconstructed .msg file
-q Suppress informational messages
-t Include text in .h output
HELP
exit;
}
my( $in, $out ) = ( \*STDIN, \*STDOUT );
my $infile = '-';
my $at;
my %symtab = ( fac => { name => {}, num => {}, }, # Facility name <=> number
msgno => {}, # By message number
name => {}, # By name
literals => {}, # Unresolved literal expressions
);
my $facname;
my $facno = 0;
my $sharedfac = 0;
my $sysfac = '';
my $nextmsgno = 1;
my $defsev;
# Magic numbers in condition codes:
#
# F000 0000 - Control bits, ignored here
# 0800 0000 - Customer-assigned code (not /SYSTEM, no $)
# 07FF 0000 - Facility number [0-2047]
# 0000 8000 - Message is facility-specific (not /SHARED)
# 0000 7FFC - Message number [0-4095) (without facility-specific bit)
# 0000 0007 - Message severity code
#
# Within the DEC-controlled shared message space:
# 4096 - 8191 - Shared message numbers (SHR$ prefix, facility 0, no severity encoded)
# 0 - 4095, 8192 - 32767 - System service message number (SS$ prefix, facility 0)
# - Note that >= 32768 for facility 0 are unshared, SYSTEM$ prefix, facility 0)
#
# Deviations from VMS Message:
#
# - Continuation lines are not implemented,
#
# - /PREFIX not supported - instead, the SYSTEM facility is treated as three separate
# facilities: SS, SHR and SYSTEM
#
# - .LITERAL supports limited syntax:
# only symbol = expression
# no right shift
# Perl operators not detected, Perl precedence used.
#
# - MESSAGE generates foo$_ FACILITY, even for customer facilities. message generates
# foo_FACILITY for customer facilities; this enables MOUNT$ and MOUNT to co-exist.
#
# - Convention for SHR_foo is to encode severity by adding SHR_{WARN,SUCCESS,ERROR,INFO,SEVERE}
# where used. (e.g. .LITERAL FAC_foo = (FAC_FACILITY@16) + SHR_foo + SHR_SUCCESS
#
# Severity codes as input (includes abbreviations)
my %sev = ( warning => 0, warn => 0,
success => 1,
error => 2,
info => 3, informational => 3,
severe => 4, fatal => 4 );
# Severity codes as output
my %sevnam = reverse ( warning => 0, success => 1, error => 2,
info => 3, severe => 4, fatal => 4,
undef5 => 5, undef6 => 6, undef7 => 7 );
# Select output file, backing up output if requested
if( @ARGV > 1 ) {
if( $ARGV[-1] ne '-' ) {
if( $opt_b && -f $ARGV[-1] ) {
my( $fn, $bak, $n ) = ( $ARGV[-1], '', 0 );
while( -f ($fn . $bak) ) {
$bak = '.bak';
$bak .= '.' . $n if( $n );
++$n;
}
if( move( $ARGV[-1], $fn . $bak ) ) {
print STDERR ( "Saved $ARGV[-1] as $fn$bak\n" ) unless( $opt_q );
} else {
print STDER( "Can't backup $ARGV[-1]: $!\n" );
exit;
}
}
if( !open( $out, '>', $ARGV[-1] ) ) {
print STDERR ( "Cant open $ARGV[-1]: $!\n" );
exit;
}
}
pop @ARGV;
}
# Read each input file
do {
if( @ARGV ) {
if( $ARGV[0] ne "-" ) {
undef $in;
if( !open( $in, '<', $ARGV[0] ) ) {
print STDERR ( "Cant open $ARGV[0]: $!\n" );
exit;
}
$infile = $ARGV[0];
print STDERR ( "Processing $infile\n" ) unless( $opt_q );
}
shift @ARGV;
}
while( <$in> ) {
s/\r//g;
chomp;
$at = sprintf( "at line %u of %s\n", $., $infile );
# Facility names are the portion of a symbol name before
# the first '_'. Those ending in '$' are 'system" (DEC) facilities.
# Others are customer-defined facilities.
#
# Process #define foo$_FACILITY and foo$_anything else.
# This includes things that are not messages (such as item codes)
# but those won't be used. Toss unwanted facilities.
#
# Only #define FOO$?_BAR n lines are recognized. n can be a simple
# decimal, octal or hex constant.
#
# Because decompiled message.exe files don't contain symbol or
# severity information, the #defines pre-populate the symbol table.
if( !defined($facname) &&
m/^\s*#\s*define\s*(((\w+?)([\$]?))_(\w+))\s+((?i:0x)?[[:xdigit:]]+)/ ) {
my( $symname, $facid, $facname, $factype, $id, $val ) =
( $1, $2, $3, $4, $5, $6 );
next if( $ign{$symname} || !$wanted{$facid} );
if( $val =~ /^0/ ) {
$val = oct( $val );
} else {
$val = 0+$val;
}
if( $id eq 'FACILITY' ) {
$symtab{fac}{name}{$facid} = $val;
$symtab{fac}{num}{$val} = $facid;
} else {
# Exact forward and reverse entries.
# Tentative entry for fuzzy lookup
$symtab{name}{$symname}{val} = $val;
$symtab{msgno}{$val}{exact} = $symname;
my $msgno = $val & 0x0FFFFFF8;
$symtab{msgno}{$msgno}{default}{sev} = $val & 0x7;
$symtab{msgno}{$msgno}{default}{sym} = $symname;
$symtab{msgno}{$msgno}{default}{fac} = $facid;
$symtab{msgno}{$msgno}{default}{ft} = $factype;
}
next;
}
# Process message compiler directives. These don't look like
# anything that might be found in a .h file, so there's no test
# for file type. Only process data between .FACILITY and .END
# This is a subset of the VMS Message language.
# Blank and comment lines
next if( m/^\s*(?:!.*)?$/ );
if( s{^\s*\.FACILITY\s+([^,]+),\s*([^\s/]+)}{}i ) {
$facname = uc $1;
$facno = decodenum( $2, '.FACILITY' );
unless( defined $facno ) {
undef $facname;
next;
}
if( $facno >= 2048 ) {
print STDERR ("$facname facility number ($facno) is too big $at" );
undef $facname;
next;
}
if( s,/SHARED,,i ) {
$sharedfac = 1;
} else {
$sharedfac = 0;
}
if( s,/SYSTEM,,i ) {
$sysfac = '$';
} else {
$sysfac = '';
}
s/\s*!.*$//;
s/\s+//g;
if( length ) {
print STDERR ( "Unrecognized .FACILITY directive content '$_' for $facname $at\n" );
}
$facno |= 0x0800 unless( $sysfac );
my @fac = ( $facname );
push @fac, ( qw/SS SHR/ ) if( $sysfac &&
( $facname eq 'SYSTEM' ||
$facname eq 'SHR' ) );
foreach my $fac (@fac ) {
my $facid = $fac . $sysfac;
if( $wanted{$facid} ) {
$symtab{fac}{name}{$facid} = $facno;
$symtab{fac}{num}{$facno} = $facid;
} elsif( !$opt_q ) {
printf STDERR ( ".FACILITY %s is not wanted, will not process $at", $facid );
}
}
$nextmsgno = 1;
undef $defsev;
next;
}
if( defined($facname) && m/^\s*\.END\b/i ) {
undef $facname;
next;
}
next unless( defined $facname && $wanted{$facname.$sysfac} );
if( m/^\s*\.IDENT\b.*$/i ) {
next;
}
if( m/^\s*\.PAGE\b.*$/i ) {
next;
}
if( m/^\s*\.TITLE\b.*$/i ) {
next;
}
if( s/^\s*\.BASE\s+(\S+)//i ) {
$nextmsgno = decodenum( $1, '.BASE' );
unless( defined $nextmsgno ) {
undef $facname;
next;
}
if( $nextmsgno > 4095 ) {
undef $facname;
printf STDERR ("Message number too big for .BASE $at");
next;
}
s/\s*!.*$//;
s/\s+//g;
if( length ) {
printf STDERR ("Unrecognized content in .BASE directive $at");
undef $facname;
next;
}
next;
}
if( s/^\s*\.SEVERITY\s+(WARNING|SUCCESS|ERROR|INFO|SEVERE)\b//i ) {
unless( exists $sev{lc $1} ) {
print STDERR ("Unknown severity $1 in .SEVERITY $at" );
undef $facname;
next;
}
s/\s*!.*$//;
s/\s+//g;
if( length ) {
printf STDERR ("Unrecognized content in .SEVERITY $at");
undef $facname;
next;
}
$defsev = $sev{lc $1};
next;
}
if( m/^\s*\.LITERAL\s+([\$\w]+)\s*=\s*(.+?)\s*(?:!.*)?$/i ) { # Subset
my( $symname, $expr ) = ( uc $1, uc $2 );
# Save the original expression.
# Evaluate any simple constant, converting to decimal.
# If only simple constants, make a symbol table entry.
# Otherwise, queue for evaluation.
my $oe = $expr;
$expr =~ s/\^[dD]0*(\d+)/$1/g;
$expr =~ s/\^[xX]([[:xdigit:]]+)/oct( '0x' . $1 )/ge;
$expr =~ s/\^[oO]([0-7]+)/oct( '0' . $1 )/ge;
# Shift operator - n.b. only left shift supported here.
$expr =~ s/\@/<</g;
# See if whe have a constant
my $tmp = $expr;
$tmp =~ s/\s+//g;
# Remove any paired ()
1 while( $tmp =~ s/^\((.*)\)\s*$/$1/ );
if( $tmp =~ /^\d+$/ ) {
my $val = 0 + $tmp;
my( undef, $facid, $facname, $factype, $id ) =
$symname =~ /^(((\w+?)([\$]?))_(\w+))$/;
$factype ||= '';
if( $id eq 'FACILITY' ) {
$symtab{fac}{name}{$facid} = $val;
$symtab{fac}{num}{$val} = $facid;
} else {
$symtab{name}{$symname}{val} = $val;
my $msgno = $val & 0x0FFFFFF8;
$symtab{msgno}{$val}{exact} = $symname;
$symtab{msgno}{$msgno}{default}{sev} = $val & 0x7;
$symtab{msgno}{$msgno}{default}{sym} = $symname;
if( defined $facid ) {
$symtab{msgno}{$msgno}{default}{fac} = $facid;
$symtab{msgno}{$msgno}{default}{ft} = $factype;
}
}
} else {
$symtab{literals}{uc $symname}{expr} = $expr;
$symtab{literals}{uc $symname}{oe} = $oe;
$symtab{literals}{uc $symname}{at} = $at;
}
next;
}
# Evaluate message lines
my( $id, $symname, $msgno, $text, $mods);
# Names are assigned sequentially following last or .BASE
# message compiled. The symbols from decompiled .MSG files
# are simply the message number in hex preceeded by _. As these are
# not useful to humans, we don't treat those as "real" symbols, and
# attempt to find a better name.
#
# Simplification: Qualifiers are only processed after the message text.
if( s/^\s*(\w+)\s+<([^>]+)>\s*(.*)$// ||
s/^\s*(\w+)\s+"([^"]+)"\s*(.*)$// ) {
if( $nextmsgno > 4096 ) {
undef $facname;
printf STDERR ("Message number $nextmsgno too big $at");
next;
}
$msgno = ($nextmsgno << 3) | ($facno << 16) | (!$sharedfac << 15);
$nextmsgno = (($msgno & 0x7FF8) >> 3) +1;
$symname = uc $1;
$text = $2;
$mods = uc $3;
undef $symname if( $symname =~ /^_[[:xdigit:]]{8}$/ );
} else {
printf STDERR ( "Ignored unrecognized line '$_' in message definitions $at" );
next;
}
my( $fac, $facid, $shrmsg );
my( $mt, $msgsev, $fao, $other );
# Classify message as system or user and shared or not
$mt = ($msgno & 0x08000000)? '': '$';
$shrmsg = !($msgno & 0x8000);
if( $shrmsg && $mt eq '$' ) {
if( $msgno >= 4096 && $msgno < 8192 ) {
$fac = 'SHR';
} else {
$fac = 'SS';
}
} elsif( $facno == 0 ) {
$fac = 'SYSTEM';
} else {
$fac = $facname;
}
$facid = $fac . $mt;
# Qualifiers:
# Assign message ID, which is the symbol name unless /IDENT specified
$id = $symname;
if( $mods =~ s,/IDENT(?:IFICATION)?[=:](\S+),,i ) {
$id = $1;
}
# Severity: from .SEVERITY or qualifier
# May be updated later from a registered symbol
$msgsev = $defsev;
if( $mods =~ s,/(SUCCESS|INFO(?:RMATIONAL)|WARN(?:ING)|ERROR|SEVERE|FATAL),,i ) {
$msgsev = $sev{lc $1};
}
# FAO argument count
$fao = 0;
if( $mods =~ s,/FAO(?:_COUNT)?[=:]([^\s/]+),,i ) {
$fao = decodenum( $1, '/FAO_COUNT' );
unless( defined $fao ) {
undef $facname;
next;
}
}
# Catch any unimplemented or unrecognized qualifiers
$mods =~ s/^\s+//g;
$mods =~ s/\s*!.*$//;
$mods = s/\s+$//g;
if( length $mods ) {
printf STDERR ("Unrecognized content '$mods' in message definition $at");
undef $facname;
next;
}
if( !defined $msgsev ) {
print STDERR ( "No .SEVERITY or /<severity> found for message $symname $at" );
undef $facname;
next;
}
$msgno |= 0+$msgsev;
# Grab the real severity and symbol from a #define if we have one.
if( defined( $symname ) ) {
$symname = $facid . '_' . $symname;
} elsif( exists $symtab{msgno}{$msgno}{exact} ) {
$symname = $symtab{msgno}{$msgno}{exact};
$msgno = ($msgno & ~0x7) | $symtab{msgno}{$msgno}{default}{sev};
} elsif( exists $symtab{msgno}{$msgno}{default} ) {
my $def = $symtab{msgno}{$msgno}{default};
if( $def->{fac} eq $facid ) {
$symname = $def->{sym};
$msgno = ($msgno & ~0x7) | $def->{sev};
}
}
# At this point, the symbol name should have been found.
# It's undefined if it came from an unmessage symbol.
# Otherwise, it's either explicit in the definition line,
# or found in the symbol table.
if( !defined $symname ) {
if( !defined( $id ) ) {
print STDERR ( "Neither symbol nor id found in $_ in $facname at line $.\n" );
undef $facname;
next;
}
$symname = $facid . '_' . $id;
}
# Ignore handles the wierd cases of message codes without text
# aliased to numbers that have text. SS$_WASSET is the classic.
next if( $ign{$symname} );
unless( $opt_m ) {
# Attempt to convert FAO directives to printf. These aren't
# exact. N.B. FAO count == 0 can still have other directives.
# Quote %, \ and " for C
$text =~ s/%/%%/g;
$text =~ s,\\,\\\\,g;
$text =~ s,",\\",g;
# ASCII directives map to %s
# -- one arg: Counted, Descriptor, ASCIZ
$text =~ s/!(\d+)?A[CSZ]/'%' . (defined $1? "-$1": '') . 's'/ge;
# -- len, addr: ASCII, Formatted ASCII
$text =~ s/!(\d+)?A[DF]/'%' . (defined $1? "-$1": '') . '.*s'/ge;
# Numeric: PRIxN codes must be listed in genmsg
# Octal of various sizes, 0-fill, signed, optional field width
# Oddly, there is no space-fill or minimal width directive for
# octal or hex.
$text =~ s/!(\d+)?OB/'%0' . (defined $1? $1:3) . '"PRIo8"'/ge;
$text =~ s/!(\d+)?OW/'%0' . (defined $1? $1:6) . '"PRIo16"'/ge;
$text =~ s/!(\d+)?OL/'%0' . (defined $1? $1:11) . '"PRIo32"'/ge;
$text =~ s/!(\d+)?OQ/'%0' . (defined $1? $1:22) . '"PRIo64"'/ge;
$text =~ s/!(\d+)?OA/'%0' . (defined $1? $1:11) . '%p"'/ge;
# Decimal signed
$text =~ s/!(\d+)?ZB/'%0' . (defined $1? $1:3) . '"PRIu8"'/ge;
$text =~ s/!(\d+)?ZW/'%0' . (defined $1? $1:6) . '"PRIu16"'/ge;
$text =~ s/!(\d+)?ZL/'%0' . (defined $1? $1:11) . '"PRIu32"'/ge;
$text =~ s/!(\d+)?ZQ/'%0' . (defined $1? $1:22) . '"PRIu64"'/ge;
$text =~ s/!(\d+)?ZA/'%0' . (defined $1? $1:11) . '%p"'/ge;
# Hex
$text =~ s/!(\d+)?XB/'%0' . (defined $1? $1:2) . '"PRIX8"'/ge;
$text =~ s/!(\d+)?XW/'%0' . (defined $1? $1:4) . '"PRIX16"'/ge;
$text =~ s/!(\d+)?XL/'%0' . (defined $1? $1:8) . '"PRIX32"'/ge;
$text =~ s/!(\d+)?XQ/'%0' . (defined $1? $1:16) . '"PRIX64"'/ge;
# Unsigned, space fill
$text =~ s/!(\d+)?UB/'%' . (defined $1? $1:'') . '"PRIu8"'/ge;
$text =~ s/!(\d+)?UW/'%' . (defined $1? $1:'') . '"PRIu16"'/ge;
$text =~ s/!(\d+)?UL/'%' . (defined $1? $1:'') . '"PRIu32"'/ge;
$text =~ s/!(\d+)?UQ/'%' . (defined $1? $1:'') . '"PRIu64"'/ge;
# Decimal, space fill
$text =~ s/!(\d+)?SB/'%' . (defined $1? $1:'') . '"PRId8"'/ge;
$text =~ s/!(\d+)?SW/'%' . (defined $1? $1:'') . '"PRId16"'/ge;
$text =~ s/!(\d+)?SL/'%' . (defined $1? $1:'') . '"PRId32"'/ge;
$text =~ s/!(\d+)?SQ/'%' . (defined $1? $1:'') . '"PRId64"'/ge;
# CR-LF
$text =~ s/!\//\\n/g;
# Arg number movements (Not supported, but remove the directive)
# Might handle with %n$, but that's not supported everwhere, and
# fortunately, no message that we use requires it.
$text =~ s/!(?:\d+)?(?:\+|--)//g;
# Character expansion
$text =~ s/!(\d+)?\*(.)/defined $1? ($2 x $1) : $1/ge;
# Special items: Date, Time, plural, UIC
# The %% is because of the % substitution earlier
$text =~ s/!%%D/%s/g;
$text =~ s/!%%T/%s/g;
$text =~ s/!%%S/(s)/g;
$text =~ s/!%%U/[%o,%o]/g;
# n-ary IF / ELSE /FI
if( 0 ) {
$text =~ s/!(\d+)%%C//g;
$text =~ s/!%%E//g;
$text =~ s/!%%F//g;
}
# !! => !
$text =~ s/!!/!/g;
if( $text =~ /!/ ) {
print STDERR ( "Unknown \$FAO code in $text\n" );
}
}
# Detect duplicate symbols
# Ignore differences caused by fuzzy symbol entries.
if( exists $symtab{name}{$symname} &&
$symtab{name}{$symname}{val} != $msgno ) {
my( $v1, $v2 ) = ( $symtab{name}{$symname}{val}, $msgno );
if( (($v1 ^ $v2) & 0x0FFFFFF8) ||
((($v1 ^ $v2)& 0x7) != 0 && ($v1 & 0x7) != 0) ) {
my $n = ++$symtab{name}{$symname}{dups};
my $len;
unless( $opt_q ) {
printf STDERR ( "Duplicate symbol $symname, using $symname.$n instead $at" );
$len = length( $symname . $n );
printf STDERR ( " %-*s %08x %u\n", $len, $symname,
$symtab{name}{$symname}{val},
$symtab{name}{$symname}{val} );
}
$symname .= $n;
unless( $opt_q ) {
printf STDERR ( " %-*s %08x %u\n", $len, $symname, $msgno, $msgno );
printf STDERR ( " %-*s %08x\n", $len, 'XOR', ($v1+0) ^ ($v2+0) );
}
}
}
# Create symbol table entry for this message
$symtab{name}{$symname}{val} = $msgno;
$symtab{msgno}{$msgno}{default}{fac} = $facid;
$symtab{msgno}{$msgno}{default}{ft} = $mt;
$symtab{msgno}{$msgno}{default}{sev} = $msgno & 0x7;
$symtab{msgno}{$msgno}{exact} = $symname;
$symtab{msgno}{$msgno}{sym} = $symname;
$symtab{msgno}{$msgno}{text} = $text;
$symtab{msgno}{$msgno}{fao} = $fao;
$symtab{msgno}{$msgno}{id} = $id;
} # Each line of file
if( $in != \*STDIN ) {
close( $in );
printf STDERR ("\n" ) unless( $opt_q );
undef $infile;
}
undef $facname;
$facno = 0;
$sharedfac = 0;
$nextmsgno = 1;
undef $defsev;
} while( @ARGV );
# Decode Message language number
# ^D - decimal
# ^O - octal
# ^X - hex
#
# Default is decimal
#
# Not supported: ^O( 10 + 10 ); (^O10 + ^O10) is OK
sub decodenum {
my $num = shift;
my $thing = shift;
if( $num =~ /^\s*\^[xX]([[:xdigit:]]+)\s*(?:!.*)?$/ ) {
return oct( '0x' . $1);
}
if( $num =~ /^\s*\^[oO]([0-7]+)\s*(?:!.*)?$/ ) {
return oct( '0' . $1);
}
if( $num =~ /^\s*(?:\^[dD])?0*([0-9]+)\s*(?:!.*)?$/ ) {
return 0+$num;
}
printf STDERR ( "Unrecognized value for $thing $at" );
return undef;
}
sub msgsort { # Increasing group shared, then severity, msg # within group
return
($a & 0x8000) <=> ($b & 0x8000) ||
($b & 1) <=> ($a & 1) ||
($a & 6) <=> ($b & 6) ||
($a & 0xFFF8) <=> ($b & 0xFFF8);
}
# Resolve literal expressions
#
# This isn't a full implementation of the VMS message language.
# The more important points:
# No right shift (- @)
# Perl precedence - and Perl operators illegal in Message accepted
# No multiple assignments/directive or default values
# This is intended to be just enough to allow expressions of
# the form ( (foo$_facility@16) + SHR$_bar + 4
# to enable defining symbols so shared messages can be used.
sub symval {
my( $v, $pass ) = @_;
if( $v =~ /^\d+$/ ) {
return 0+$v;
}
if( exists $symtab{name}{$v} &&
exists $symtab{name}{$v}{val} ) {
return $symtab{name}{$v}{val};
}
if( $v =~ /^([^_]+)_FACILITY$/ && exists $symtab{fac}{name}{$1} ) {
return $symtab{fac}{name}{$1};
}
return $v if( $pass < 2 );
return "Undef";
}
for( my $pass = 1; $pass <= 2; $pass++ ) {
foreach my $symname (keys%{$symtab{literals}}) {
my( $expr, $oe, $at ) = @{$symtab{literals}{$symname}}{qw/expr oe at/};
$expr =~ s{\b([\$\w]+)\b}{symval($1, $pass)}gex;
if( $expr =~ m{[^()+*\@/\d\s-]} && $pass < 2 ) {
$symtab{literals}{$symname}{expr} = $expr;
next;
}
if( $expr =~ /Undef/ ) {
chomp $at;
printf STDERR ( "Undefined symbol in expression $at: $oe => $expr\n" );
} else {
my $val = eval "$expr";
if( $@ ) {
chomp $at;
printf STDERR ( "Unable to evaluate expression $at: $oe => $expr: $@\n" );
} else {
my( undef, $facid, $facname, $factype, $id ) =
$symname =~ /^(((\w+?)([\$]?))_(\w+))$/;
$factype ||= '';
unless( defined $id ) {
print STDERR ( "$symname can't be parsed (should be facility$?_id) $at" );
next;
}
if( $id eq 'FACILITY' ) {
$symtab{fac}{name}{$facid} = $val;
$symtab{fac}{num}{$val} = $facid;
} else {
$symtab{name}{$symname}{val} = $val;
my $msgno = $val & 0x0FFFFFF8;
$symtab{msgno}{$val}{exact} = $symname;
$symtab{msgno}{$msgno}{default}{sev} = $val & 0x7;
$symtab{msgno}{$msgno}{default}{sym} = $symname;
if( defined $facid ) {
$symtab{msgno}{$msgno}{default}{fac} = $facid;
$symtab{msgno}{$msgno}{default}{ft} = $factype;
}
}
}
}
}
}
if( $opt_m ) {
# Generate a .msg file as output
# Sort and format for human consumption.
printf( << 'HEADING', $VERSION, $argv );
! This is an automatically-generated file. message version %s
!
! Command: message %s
!
! It uses a subset of the VMS Message utility language.
! The message definitions can be processed by the message
! Perl script, which will compile them into a .h file usable
! by sysmsg.c and all the ODS2 modules that rely on condition
! codes.
!
! This technlology was developed by Timothe Litt, and is
! free to use with the ODS2 package, originally developed by
! Paul Nankervis <Paulnank@au1.ibm.com>.
!
! ODS2 may be freely distributed within the VMS community
! providing that the contributions of the original author and
! subsequent contributors are acknowledged in the help text and
! source files. This is free software; no warranty is offered,
! and while we believe it to be useful, you use it at your own risk.
!
HEADING
my $infac;
foreach my $facid ( sort keys %{$symtab{fac}{name}} ) {
my $facno = $symtab{fac}{name}{$facid};
my( $facname, $sharedfac, $nextmsgno, $lastsev );
$facname = $facid;
$facname =~ s/\$$//;
$sharedfac = 100;
$nextmsgno = 1;
$lastsev = 99;
foreach my $msgno (sort msgsort grep {
exists $symtab{msgno}{$_}{default}{fac} &&
$symtab{msgno}{$_}{default}{fac} eq $facid }
keys %{ $symtab{msgno}}) {
my $msg = $symtab{msgno}{$msgno};
my $mshr = (~(0+$msgno)) & 0x8000;
# New facility (also if /SHARED changes)
my $mt = ($facno & 0x0800)? '': '$';
if( $mshr != $sharedfac ) {
print( "\n .END\n" ) if ($infac );
printf( "\n .FACILITY %s,%u",
$facname,
$facno & ~0x0800 );
if( !($facno & 0x0800) ) {
print( " /SYSTEM" );
}
if( $mshr ) {
print( " /SHARED" );
}
print( "\n" );
$sharedfac = $mshr;
$infac = 1;
}
# Generate .LITERAL for symbols without message text
if( !exists $msg->{text} ) { # Typ. Shared message codes
next if( exists $msg->{default}{sev} && # This partial def was resolved
exists $symtab{msgno}{($msgno & ~7) |
$msg->{default}{sev}}{text} );
my $symnam = $msg->{default}{sym};
printf( " .LITERAL %-*s = (",
(length($facid)+1+15),
$symnam );
# Try to create a symbolic expression for the value
my( $mfac, $mnum, $msev ) = (
($msgno & 0x0FFF0000) >> 16,
($msgno & 0xFFF8),
$msgno & 7 );
my $expr = '';
if( !($msgno & 0x8000) ) {
if( $mfac ) {
my $fs = $mfac;
$fs = $symtab{fac}{num}{$mfac} . '_FACILITY'
if( exists $symtab{fac}{num}{$mfac} );
$msev = $symtab{msgno}{$msgno}{default}{sev}
if( exists $symtab{msgno}{$msgno}{default}{sev} );
$expr .= '(' . $fs . '@16)';
if( exists
$symtab{msgno}{$msgno}{exact} &&
$symtab{msgno}{$msgno}{exact} ne $symnam ) {
$expr .= ' + '. $symtab{msgno}{$msgno}{exact};
} elsif( exists
$symtab{msgno}{$mnum | $msev}{exact} &&
$symtab{msgno}{$mnum | $msev}{exact} ne $symnam ) {
$expr .= ' + '.
$symtab{msgno}{$mnum | $msev}{exact};
} elsif( exists
$symtab{msgno}{$mnum}{exact} &&
$symtab{msgno}{$mnum}{exact} ne $symnam ) {
$expr .= ' + '.
$symtab{msgno}{$mnum}{exact};
$expr .= ' + '. $msev;
} elsif( $mnum ) {
$expr .= sprintf( ' + (^D%u@3) + %u',
($mnum >> 3), $msev );
} else {
$expr .= sprintf( '+ %u', $msev );
}
}
}
unless( length $expr ) {
if( exists $msg->{default}{sev} ) {
$expr = ($msgno & ~7) | $msg->{default}{sev};
} else {
$expr = $msgno;
}
}
$expr .= ')';
printf( "%-50s ! 0x%08x %s\n", $expr,
($mfac << 16) | $mnum | $msev, ucfirst $sevnam{$msev} );
if( $msgno & 0x8000 || $symtab{fac}{name}{$facid} == 0 ) {
print STDERR ( "No text found for $msg->{default}{sym}\n" );
next;
}
next;
}
# See if a new .SEVERITY is necessary
my $thissev = $msgno & 7;
my $nl = 1;
if( $thissev != $lastsev ) {
printf( "\n .SEVERITY %s\n", uc $sevnam{$thissev} );
$lastsev = $thissev;
$nl = 0;
}
# Or a new .BASE
my $mno = ($msgno & 0x7FFF) >> 3;
if( $mno != $nextmsgno ) {
if( $nl ) {
print( "\n" );
$nl = 0;
}
printf( " .BASE %u\n", $mno );
}
$nextmsgno = $mno + 1;
# Message definition line
{
my @delim = $msg->{text} =~ /[<>]/? ('"', '"'): ('<', '>');
my( $symname, $facid, $facname, $factype, $sid ) =
$msg->{sym} =~ /^(((\w+?)([\$]?))_(\w+))$/;
printf( "%-16s %s%s%s",
$sid,
$delim[0], $msg->{text}, $delim[1] );
if( $msg->{id} ne $sid ) {
printf( " /IDENT=%s", $msg->{id} );
}
}
if( $msg->{fao} ) {
printf( " /FAO=%u", $msg->{fao} );
}
print( "\n" );
} # each message number in facility
print( "\n .END\n" ) if( $infac );
$infac = 0;
print ("\n" );
} # each facility
exit;
}
# Output the header file in sections:
#
# Prolog
# Standard macro definitions for condition names
# Entries for the facility definition structure
# Entries for the message text definition structure
#
# The last two are enabled by a magic #define in the caller.
printf( << '__EOF', $VERSION, $argv );
#ifndef _SDEF_H
#define _SSDEF_H
/* ** MACHINE GENERATED FILE by message version %s: ODS2 Message catalog */
/* Command: message %s
*/
/*
* This is part of ODS2 written by Paul Nankervis,
* email address: Paulnank@au1.ibm.com
*
* ODS2 may be freely distributed within the VMS community
* providing that the contributions of the original author and
* subsequent contributors are acknowledged in the help text and
* source files. This is free software; no warranty is offered,
* and while we believe it to be useful, you use it at your own risk.
*
__EOF
print( << '__EOF' ) if( $opt_t );
* This file contains #defines for condition codes as well as initialization
* data for the structures used to print them. It is compiled from VMS
* Message utility language, however the translations from $FAO to printf
* are approximate, which means that those messages sourced from VMS
* may not work in exactly the same way if compiled with VMS Message.
*
__EOF
printf( << '__EOF' );
* Note: This file was automatically generated, and may contain many
* messages that are not used by ODS2. It was easier than finding each one.
* To rebuild it using the VMS files, you need some sources from a standard
* VMS system. But you can probably skip to the SHORT procedure...unless
* you are producing an I18n translation, and want the translated VMS messages...
*
* The full procedure is:
*
* On a VMS system:
* Obtain unmessage for VMS from a SIG tape, Freeware CD, or
* ftp://ftp.decuserve.org/decus/vms/sig_tapes/vax88b1/meadows/unmessage/unmessage.exe
*
* $ set command unmessage.cld
* $ unmessage sys$message:sysmsg.exe /output=vms_msgdump.msg
*
* $ copy sys$library:*.h vms_msgdefs.h
*
* The rest of the process does not require a VMS system:
*
* You can run the Perl script "message" on any convenient machine.
*
* perl message -b vms_msgdefs.h ods2.msg vms_msgdump.msg ssdef.h
*
* Note "ods2.msg" in the middle; this is the ODS2 message catalog and
* ssdef.h (the last file) is the output.
*
* Symbols come from the #defines in the .h files, which also contribute the
* message severity codes. The output is this .h file, which includes the
__EOF
if( $opt_t ) {
printf( << '__EOF' );
* #defines and table data used by sysmsg.c.
__EOF
} else {
printf( << '__EOF' );
* #defines for message codes. To get the message definitions, use message -t.
__EOF
}
printf( << '__EOF' );
*
* The compiler can also produce a .msg file as output (use -m). This
* provides a reconstructed .msg file with the correct severity codes and
* IDENT mappings. This can be used to populate ods2msg.msg by removing
* unsused messages
*
* perl message -bm vms_msgdefs.h vms_msgdump.msg vms_messages.msg
*
* At this point, you can get just the VMS messages in ssdef.h form with:
* perl message -b vms_messages.msg vms_messages.h
*
* But you really want the ODS2 version:
* perl message -b vms_messages.msg ods2.msg ssdef.h
*
* Currently, the interesting VMS messages are distributed as vms_messages.msg,
* so the SHORT procedure is:
* perl message -b vms_message.msg ods2.msg ssdef.h.
*
*/
__EOF
# define foo$?_FACILITY
my $maxflen = 0;
foreach my $facid (keys %{$symtab{fac}{name}} ) {
my $len = length( $facid . '_FACILITY' );
$maxflen = $len if( $len > $maxflen );
}
foreach my $facid ( sort keys %{$symtab{fac}{name}} ) {
printf( "#define %-*s %u\n", $maxflen,
$facid . '_FACILITY', $symtab{fac}{name}{$facid} );
}
# define other symbols
my $maxslen = 0;
foreach my $symname (keys %{$symtab{name}}) {
my $len = length $symname;
$maxslen = $len if( $len > $maxslen );
}
foreach my $symname (sort keys %{$symtab{name}}) {
my $val = $symtab{name}{$symname}{val};
unless( defined $val ) {
print STDERR ("Undefined value for $symname\n");
next;
}
printf( "#define %-*s %9u", $maxslen, $symname, $val );
printf( " /* %08x %s */", $val, ucfirst $sevnam{$val & 7} );
print( "\n" );
}
print( "#endif\n" );
exit unless( $opt_t );
# Facility table
print( "#ifdef GENERATE_FACTAB\n#undef GENERATE_FACTAB\n/* { code, \"string\" }, */\n" );
foreach my $facid ( sort keys %{$symtab{fac}{name}} ) {
my $facno = $symtab{fac}{name}{$facid};
printf( "FAC(%-*s, %-*s) /* %-4u",
$maxflen, $facid . '_FACILITY',
$maxflen, ($facid =~ /\$$/)? substr( $facid, 0, -1 ): $facid,
$facno & 0x07FF );
if( $facno & 0x0800 ) {
printf( " + customer = %-4u", $symtab{fac}{name}{$facid} );
} else {
print( " " );
}
print (" */\n" );
}
print( "#endif\n" );
# Message table
print( "#ifdef GENERATE_MSGTAB\n#undef GENERATE_MSGTAB\n/* MSG( code, facility, \"text \", #args) */\n" );
my $maxilen = 0;
$maxslen = 0;
foreach my $msgno (keys %{$symtab{msgno}}) {
my $msg = $symtab{msgno}{$msgno};
if( exists $msg->{text} ) {
my $len = length $msg->{id};
$maxilen = $len if( $len > $maxilen );
$len = length $msg->{sym};
$maxslen = $len if( $len > $maxslen );
}
}
foreach my $msgno (sort msgsort keys %{$symtab{msgno}}) {
my $msg = $symtab{msgno}{$msgno};
if( exists $msg->{text} ) {
my $symname;
my $m = 'MSG(' . $msg->{sym} . (' ' x ($maxslen - length $msg->{sym})) .
', ' . $msg->{id} . ( ' ' x ($maxilen - length $msg->{id})) .
', "' . $msg->{text} . '", ' . $msg->{fao} . ')';
if( length $m > 95 ) {
$m .= "\n" . (' ' x 95 );
} else {
$m .= ' ' x (95 - length( $m ));
}
$m .= sprintf( "/* %08x %s */\n", $msgno, ucfirst $sevnam{$msgno & 0x7} );
print( $m );
}
}
print( "#endif\n" );
exit;