xref: /openbsd-src/gnu/usr.bin/perl/dist/Unicode-Normalize/mkheader (revision 256a93a44f36679bee503f12e49566c2183f6181)
15759b3d2Safresh1#!perl
25759b3d2Safresh1#
35759b3d2Safresh1# This auxiliary script makes five header files
45759b3d2Safresh1# used for building XSUB of Unicode::Normalize.
55759b3d2Safresh1#
65759b3d2Safresh1# Usage:
75759b3d2Safresh1#    <do 'mkheader'> in perl, or <perl mkheader> in command line
85759b3d2Safresh1#
95759b3d2Safresh1# Input files:
105759b3d2Safresh1#    unicore/CombiningClass.pl (or unicode/CombiningClass.pl)
115759b3d2Safresh1#    unicore/Decomposition.pl (or unicode/Decomposition.pl)
125759b3d2Safresh1#
135759b3d2Safresh1# Output files:
145759b3d2Safresh1#    unfcan.h
155759b3d2Safresh1#    unfcpt.h
165759b3d2Safresh1#    unfcmb.h
175759b3d2Safresh1#    unfcmp.h
185759b3d2Safresh1#    unfexc.h
195759b3d2Safresh1#
205759b3d2Safresh1use 5.006;
215759b3d2Safresh1use strict;
225759b3d2Safresh1use warnings;
235759b3d2Safresh1use Carp;
245759b3d2Safresh1use File::Spec;
255759b3d2Safresh1use SelectSaver;
265759b3d2Safresh1
275759b3d2Safresh1our $PACKAGE = 'Unicode::Normalize, mkheader';
285759b3d2Safresh1
295759b3d2Safresh1our $prefix = "UNF_";
305759b3d2Safresh1our $structname = "${prefix}complist";
315759b3d2Safresh1
325759b3d2Safresh1# Starting in v5.20, the tables in lib/unicore are built using the platform's
33*256a93a4Safresh1# native character set for code points 0-255.  But in v5.35, pack U stopped
34*256a93a4Safresh1# trying to compensate
35*256a93a4Safresh1*pack_U = ($] ge 5.020 && $] lt 5.035)
36*256a93a4Safresh1          ? sub { return pack('U*', map { utf8::unicode_to_native($_) } @_); }
375759b3d2Safresh1          : sub { return pack('U*', @_); };
385759b3d2Safresh1
395759b3d2Safresh1# %Canon and %Compat will be ($codepoint => $hexstring) after _U_stringify()
405759b3d2Safresh1our %Comp1st;	# $codepoint => $listname  : may be composed with a next char.
415759b3d2Safresh1our %CompList;	# $listname,$2nd  => $codepoint : composite
425759b3d2Safresh1
435759b3d2Safresh1##### The below part is common to mkheader and PP #####
445759b3d2Safresh1
455759b3d2Safresh1our %Combin;	# $codepoint => $number    : combination class
465759b3d2Safresh1our %Canon;	# $codepoint => \@codepoints : canonical decomp.
475759b3d2Safresh1our %Compat;	# $codepoint => \@codepoints : compat. decomp.
485759b3d2Safresh1our %Compos;	# $1st,$2nd  => $codepoint : composite
495759b3d2Safresh1our %Exclus;	# $codepoint => 1          : composition exclusions
505759b3d2Safresh1our %Single;	# $codepoint => 1          : singletons
515759b3d2Safresh1our %NonStD;	# $codepoint => 1          : non-starter decompositions
525759b3d2Safresh1our %Comp2nd;	# $codepoint => 1          : may be composed with a prev char.
535759b3d2Safresh1
545759b3d2Safresh1# from core Unicode database
555759b3d2Safresh1our $Combin = do "unicore/CombiningClass.pl"
565759b3d2Safresh1    || do "unicode/CombiningClass.pl"
575759b3d2Safresh1    || croak "$PACKAGE: CombiningClass.pl not found";
585759b3d2Safresh1our $Decomp = do "unicore/Decomposition.pl"
595759b3d2Safresh1    || do "unicode/Decomposition.pl"
605759b3d2Safresh1    || croak "$PACKAGE: Decomposition.pl not found";
615759b3d2Safresh1
625759b3d2Safresh1# CompositionExclusions.txt since Unicode 3.2.0.  If this ever changes, it
635759b3d2Safresh1# would be better to get the values from Unicode::UCD rather than hard-code
645759b3d2Safresh1# them here, as that will protect from having to make fixes for future
655759b3d2Safresh1# changes.
665759b3d2Safresh1our @CompEx = qw(
675759b3d2Safresh1    0958 0959 095A 095B 095C 095D 095E 095F 09DC 09DD 09DF 0A33 0A36
685759b3d2Safresh1    0A59 0A5A 0A5B 0A5E 0B5C 0B5D 0F43 0F4D 0F52 0F57 0F5C 0F69 0F76
695759b3d2Safresh1    0F78 0F93 0F9D 0FA2 0FA7 0FAC 0FB9 FB1D FB1F FB2A FB2B FB2C FB2D
705759b3d2Safresh1    FB2E FB2F FB30 FB31 FB32 FB33 FB34 FB35 FB36 FB38 FB39 FB3A FB3B
715759b3d2Safresh1    FB3C FB3E FB40 FB41 FB43 FB44 FB46 FB47 FB48 FB49 FB4A FB4B FB4C
725759b3d2Safresh1    FB4D FB4E 2ADC 1D15E 1D15F 1D160 1D161 1D162 1D163 1D164 1D1BB
735759b3d2Safresh1    1D1BC 1D1BD 1D1BE 1D1BF 1D1C0
745759b3d2Safresh1);
755759b3d2Safresh1
765759b3d2Safresh1# definition of Hangul constants
775759b3d2Safresh1use constant SBase  => 0xAC00;
785759b3d2Safresh1use constant SFinal => 0xD7A3; # SBase -1 + SCount
795759b3d2Safresh1use constant SCount =>  11172; # LCount * NCount
805759b3d2Safresh1use constant NCount =>    588; # VCount * TCount
815759b3d2Safresh1use constant LBase  => 0x1100;
825759b3d2Safresh1use constant LFinal => 0x1112;
835759b3d2Safresh1use constant LCount =>     19;
845759b3d2Safresh1use constant VBase  => 0x1161;
855759b3d2Safresh1use constant VFinal => 0x1175;
865759b3d2Safresh1use constant VCount =>     21;
875759b3d2Safresh1use constant TBase  => 0x11A7;
885759b3d2Safresh1use constant TFinal => 0x11C2;
895759b3d2Safresh1use constant TCount =>     28;
905759b3d2Safresh1
915759b3d2Safresh1sub decomposeHangul {
925759b3d2Safresh1    my $sindex = $_[0] - SBase;
935759b3d2Safresh1    my $lindex = int( $sindex / NCount);
945759b3d2Safresh1    my $vindex = int(($sindex % NCount) / TCount);
955759b3d2Safresh1    my $tindex =      $sindex % TCount;
965759b3d2Safresh1    my @ret = (
975759b3d2Safresh1       LBase + $lindex,
985759b3d2Safresh1       VBase + $vindex,
995759b3d2Safresh1      $tindex ? (TBase + $tindex) : (),
1005759b3d2Safresh1    );
1015759b3d2Safresh1    return wantarray ? @ret : pack_U(@ret);
1025759b3d2Safresh1}
1035759b3d2Safresh1
1045759b3d2Safresh1########## getting full decomposition ##########
1055759b3d2Safresh1
1065759b3d2Safresh1## converts string "hhhh hhhh hhhh" to a numeric list
1075759b3d2Safresh1## (hex digits separated by spaces)
1085759b3d2Safresh1sub _getHexArray { map hex, $_[0] =~ /\G *([0-9A-Fa-f]+)/g }
1095759b3d2Safresh1
1105759b3d2Safresh1while ($Combin =~ /(.+)/g) {
1115759b3d2Safresh1    my @tab = split /\t/, $1;
1125759b3d2Safresh1    my $ini = hex $tab[0];
1135759b3d2Safresh1    if ($tab[1] eq '') {
1145759b3d2Safresh1	$Combin{$ini} = $tab[2];
1155759b3d2Safresh1    } else {
1165759b3d2Safresh1	$Combin{$_} = $tab[2] foreach $ini .. hex($tab[1]);
1175759b3d2Safresh1    }
1185759b3d2Safresh1}
1195759b3d2Safresh1
1205759b3d2Safresh1while ($Decomp =~ /(.+)/g) {
1215759b3d2Safresh1    my @tab = split /\t/, $1;
1225759b3d2Safresh1    my $compat = $tab[2] =~ s/<[^>]+>//;
1235759b3d2Safresh1    my $dec = [ _getHexArray($tab[2]) ]; # decomposition
1245759b3d2Safresh1    my $ini = hex($tab[0]); # initial decomposable character
1255759b3d2Safresh1    my $end = $tab[1] eq '' ? $ini : hex($tab[1]);
1265759b3d2Safresh1    # ($ini .. $end) is the range of decomposable characters.
1275759b3d2Safresh1
1285759b3d2Safresh1    foreach my $u ($ini .. $end) {
1295759b3d2Safresh1	$Compat{$u} = $dec;
1305759b3d2Safresh1	$Canon{$u} = $dec if ! $compat;
1315759b3d2Safresh1    }
1325759b3d2Safresh1}
1335759b3d2Safresh1
1345759b3d2Safresh1for my $s (@CompEx) {
1355759b3d2Safresh1    my $u = hex $s;
1365759b3d2Safresh1    next if !$Canon{$u}; # not assigned
1375759b3d2Safresh1    next if $u == 0xFB1D && !$Canon{0x1D15E}; # 3.0.1 before Corrigendum #2
1385759b3d2Safresh1    $Exclus{$u} = 1;
1395759b3d2Safresh1}
1405759b3d2Safresh1
1415759b3d2Safresh1foreach my $u (keys %Canon) {
1425759b3d2Safresh1    my $dec = $Canon{$u};
1435759b3d2Safresh1
1445759b3d2Safresh1    if (@$dec == 2) {
1455759b3d2Safresh1	if ($Combin{ $dec->[0] }) {
1465759b3d2Safresh1	    $NonStD{$u} = 1;
1475759b3d2Safresh1	} else {
1485759b3d2Safresh1	    $Compos{ $dec->[0] }{ $dec->[1] } = $u;
1495759b3d2Safresh1	    $Comp2nd{ $dec->[1] } = 1 if ! $Exclus{$u};
1505759b3d2Safresh1	}
1515759b3d2Safresh1    } elsif (@$dec == 1) {
1525759b3d2Safresh1	$Single{$u} = 1;
1535759b3d2Safresh1    } else {
1545759b3d2Safresh1	my $h = sprintf '%04X', $u;
1555759b3d2Safresh1	croak("Weird Canonical Decomposition of U+$h");
1565759b3d2Safresh1    }
1575759b3d2Safresh1}
1585759b3d2Safresh1
1595759b3d2Safresh1# modern HANGUL JUNGSEONG and HANGUL JONGSEONG jamo
1605759b3d2Safresh1foreach my $j (0x1161..0x1175, 0x11A8..0x11C2) {
1615759b3d2Safresh1    $Comp2nd{$j} = 1;
1625759b3d2Safresh1}
1635759b3d2Safresh1
1645759b3d2Safresh1sub getCanonList {
1655759b3d2Safresh1    my @src = @_;
1665759b3d2Safresh1    my @dec = map {
1675759b3d2Safresh1	(SBase <= $_ && $_ <= SFinal) ? decomposeHangul($_)
1685759b3d2Safresh1	    : $Canon{$_} ? @{ $Canon{$_} } : $_
1695759b3d2Safresh1		} @src;
1705759b3d2Safresh1    return join(" ",@src) eq join(" ",@dec) ? @dec : getCanonList(@dec);
1715759b3d2Safresh1    # condition @src == @dec is not ok.
1725759b3d2Safresh1}
1735759b3d2Safresh1
1745759b3d2Safresh1sub getCompatList {
1755759b3d2Safresh1    my @src = @_;
1765759b3d2Safresh1    my @dec = map {
1775759b3d2Safresh1	(SBase <= $_ && $_ <= SFinal) ? decomposeHangul($_)
1785759b3d2Safresh1	    : $Compat{$_} ? @{ $Compat{$_} } : $_
1795759b3d2Safresh1		} @src;
1805759b3d2Safresh1    return join(" ",@src) eq join(" ",@dec) ? @dec : getCompatList(@dec);
1815759b3d2Safresh1    # condition @src == @dec is not ok.
1825759b3d2Safresh1}
1835759b3d2Safresh1
1845759b3d2Safresh1# exhaustive decomposition
1855759b3d2Safresh1foreach my $key (keys %Canon) {
1865759b3d2Safresh1    $Canon{$key}  = [ getCanonList($key) ];
1875759b3d2Safresh1}
1885759b3d2Safresh1
1895759b3d2Safresh1# exhaustive decomposition
1905759b3d2Safresh1foreach my $key (keys %Compat) {
1915759b3d2Safresh1    $Compat{$key} = [ getCompatList($key) ];
1925759b3d2Safresh1}
1935759b3d2Safresh1
1945759b3d2Safresh1##### The above part is common to mkheader and PP #####
1955759b3d2Safresh1
1965759b3d2Safresh1foreach my $comp1st (keys %Compos) {
1975759b3d2Safresh1    my $listname = sprintf("${structname}_%06x", $comp1st);
1985759b3d2Safresh1		# %04x is bad since it'd place _3046 after _1d157.
1995759b3d2Safresh1    $Comp1st{$comp1st} = $listname;
2005759b3d2Safresh1    my $rh1st = $Compos{$comp1st};
2015759b3d2Safresh1
2025759b3d2Safresh1    foreach my $comp2nd (keys %$rh1st) {
2035759b3d2Safresh1	my $uc = $rh1st->{$comp2nd};
2045759b3d2Safresh1	$CompList{$listname}{$comp2nd} = $uc;
2055759b3d2Safresh1    }
2065759b3d2Safresh1}
2075759b3d2Safresh1
2085759b3d2Safresh1sub split_into_char {
2095759b3d2Safresh1    use bytes;
2105759b3d2Safresh1    my $uni = shift;
2115759b3d2Safresh1    my $len = length($uni);
2125759b3d2Safresh1    my @ary;
2135759b3d2Safresh1    for(my $i = 0; $i < $len; ++$i) {
2145759b3d2Safresh1	push @ary, ord(substr($uni,$i,1));
2155759b3d2Safresh1    }
2165759b3d2Safresh1    return @ary;
2175759b3d2Safresh1}
2185759b3d2Safresh1
2195759b3d2Safresh1sub _U_stringify {
2205759b3d2Safresh1    sprintf '"%s"', join '',
2215759b3d2Safresh1	map sprintf("\\x%02x", $_), split_into_char(pack_U(@_));
2225759b3d2Safresh1}
2235759b3d2Safresh1
2245759b3d2Safresh1foreach my $hash (\%Canon, \%Compat) {
2255759b3d2Safresh1    foreach my $key (keys %$hash) {
2265759b3d2Safresh1	$hash->{$key} = _U_stringify( @{ $hash->{$key} } );
2275759b3d2Safresh1    }
2285759b3d2Safresh1}
2295759b3d2Safresh1
2305759b3d2Safresh1########## writing header files ##########
2315759b3d2Safresh1
2325759b3d2Safresh1my @boolfunc = (
2335759b3d2Safresh1    {
2345759b3d2Safresh1	name => "Exclusion",
2355759b3d2Safresh1	type => "bool",
2365759b3d2Safresh1	hash => \%Exclus,
2375759b3d2Safresh1    },
2385759b3d2Safresh1    {
2395759b3d2Safresh1	name => "Singleton",
2405759b3d2Safresh1	type => "bool",
2415759b3d2Safresh1	hash => \%Single,
2425759b3d2Safresh1    },
2435759b3d2Safresh1    {
2445759b3d2Safresh1	name => "NonStDecomp",
2455759b3d2Safresh1	type => "bool",
2465759b3d2Safresh1	hash => \%NonStD,
2475759b3d2Safresh1    },
2485759b3d2Safresh1    {
2495759b3d2Safresh1	name => "Comp2nd",
2505759b3d2Safresh1	type => "bool",
2515759b3d2Safresh1	hash => \%Comp2nd,
2525759b3d2Safresh1    },
2535759b3d2Safresh1);
2545759b3d2Safresh1
2555759b3d2Safresh1my $orig_fh = SelectSaver->new;
2565759b3d2Safresh1{
2575759b3d2Safresh1
2585759b3d2Safresh1my $file = "unfexc.h";
2595759b3d2Safresh1open FH, ">$file" or croak "$PACKAGE: $file can't be made";
2605759b3d2Safresh1binmode FH; select FH;
2615759b3d2Safresh1
2625759b3d2Safresh1    print << 'EOF';
2635759b3d2Safresh1/*
2645759b3d2Safresh1 * This file is auto-generated by mkheader.
2655759b3d2Safresh1 * Any changes here will be lost!
2665759b3d2Safresh1 */
2675759b3d2Safresh1EOF
2685759b3d2Safresh1
2695759b3d2Safresh1foreach my $tbl (@boolfunc) {
2705759b3d2Safresh1    my @temp = sort {$a <=> $b} keys %{$tbl->{hash}};
2715759b3d2Safresh1    my $type = $tbl->{type};
2725759b3d2Safresh1    my $name = $tbl->{name};
2735759b3d2Safresh1    print "$type is$name (UV uv)\n{\nreturn\n\t";
2745759b3d2Safresh1
2755759b3d2Safresh1    while (@temp) {
2765759b3d2Safresh1	my $cur = shift @temp;
2775759b3d2Safresh1	if (@temp && $cur + 1 == $temp[0]) {
2785759b3d2Safresh1	    print "($cur <= uv && uv <= ";
2795759b3d2Safresh1	    while (@temp && $cur + 1 == $temp[0]) {
2805759b3d2Safresh1		$cur = shift @temp;
2815759b3d2Safresh1	    }
2825759b3d2Safresh1	    print "$cur)";
2835759b3d2Safresh1	    print "\n\t|| " if @temp;
2845759b3d2Safresh1	} else {
2855759b3d2Safresh1	    print "uv == $cur";
2865759b3d2Safresh1	    print "\n\t|| " if @temp;
2875759b3d2Safresh1	}
2885759b3d2Safresh1    }
2895759b3d2Safresh1    print "\n\t? TRUE : FALSE;\n}\n\n";
2905759b3d2Safresh1}
2915759b3d2Safresh1
2925759b3d2Safresh1close FH;
2935759b3d2Safresh1
2945759b3d2Safresh1####################################
2955759b3d2Safresh1
2965759b3d2Safresh1my $compinit =
2975759b3d2Safresh1    "typedef struct { UV nextchar; UV composite; } $structname;\n\n";
2985759b3d2Safresh1
2995759b3d2Safresh1foreach my $i (sort keys %CompList) {
3005759b3d2Safresh1    $compinit .= "$structname $i [] = {\n";
3015759b3d2Safresh1    $compinit .= join ",\n",
3025759b3d2Safresh1	map sprintf("\t{ %d, %d }", $_, $CompList{$i}{$_}),
3035759b3d2Safresh1	    sort {$a <=> $b } keys %{ $CompList{$i} };
3045759b3d2Safresh1    $compinit .= ",\n{0,0}\n};\n\n"; # with sentinel
3055759b3d2Safresh1}
3065759b3d2Safresh1
3075759b3d2Safresh1my @tripletable = (
3085759b3d2Safresh1    {
3095759b3d2Safresh1	file => "unfcmb",
3105759b3d2Safresh1	name => "combin",
3115759b3d2Safresh1	type => "STDCHAR",
3125759b3d2Safresh1	hash => \%Combin,
3135759b3d2Safresh1	null =>  0,
3145759b3d2Safresh1    },
3155759b3d2Safresh1    {
3165759b3d2Safresh1	file => "unfcan",
3175759b3d2Safresh1	name => "canon",
3185759b3d2Safresh1	type => "char*",
3195759b3d2Safresh1	hash => \%Canon,
3205759b3d2Safresh1	null => "NULL",
3215759b3d2Safresh1    },
3225759b3d2Safresh1    {
3235759b3d2Safresh1	file => "unfcpt",
3245759b3d2Safresh1	name => "compat",
3255759b3d2Safresh1	type => "char*",
3265759b3d2Safresh1	hash => \%Compat,
3275759b3d2Safresh1	null => "NULL",
3285759b3d2Safresh1    },
3295759b3d2Safresh1    {
3305759b3d2Safresh1	file => "unfcmp",
3315759b3d2Safresh1	name => "compos",
3325759b3d2Safresh1	type => "$structname *",
3335759b3d2Safresh1	hash => \%Comp1st,
3345759b3d2Safresh1	null => "NULL",
3355759b3d2Safresh1	init => $compinit,
3365759b3d2Safresh1    },
3375759b3d2Safresh1);
3385759b3d2Safresh1
3395759b3d2Safresh1foreach my $tbl (@tripletable) {
3405759b3d2Safresh1    my $file = "$tbl->{file}.h";
3415759b3d2Safresh1    my $head = "${prefix}$tbl->{name}";
3425759b3d2Safresh1    my $type = $tbl->{type};
3435759b3d2Safresh1    my $hash = $tbl->{hash};
3445759b3d2Safresh1    my $null = $tbl->{null};
3455759b3d2Safresh1    my $init = $tbl->{init};
3465759b3d2Safresh1
3475759b3d2Safresh1    open FH, ">$file" or croak "$PACKAGE: $file can't be made";
3485759b3d2Safresh1    binmode FH; select FH;
3495759b3d2Safresh1    my %val;
3505759b3d2Safresh1
3515759b3d2Safresh1    print FH << 'EOF';
3525759b3d2Safresh1/*
3535759b3d2Safresh1 * This file is auto-generated by mkheader.
3545759b3d2Safresh1 * Any changes here will be lost!
3555759b3d2Safresh1 */
3565759b3d2Safresh1EOF
3575759b3d2Safresh1
3585759b3d2Safresh1    print $init if defined $init;
3595759b3d2Safresh1
3605759b3d2Safresh1    foreach my $uv (keys %$hash) {
3615759b3d2Safresh1	croak sprintf("a Unicode code point 0x%04X over 0x10FFFF.", $uv)
3625759b3d2Safresh1	    unless $uv <= 0x10FFFF;
3635759b3d2Safresh1	my @c = unpack 'CCCC', pack 'N', $uv;
3645759b3d2Safresh1	$val{ $c[1] }{ $c[2] }{ $c[3] } = $hash->{$uv};
3655759b3d2Safresh1    }
3665759b3d2Safresh1
3675759b3d2Safresh1    foreach my $p (sort { $a <=> $b } keys %val) {
3685759b3d2Safresh1	next if ! $val{ $p };
3695759b3d2Safresh1	for (my $r = 0; $r < 256; $r++) {
3705759b3d2Safresh1	    next if ! $val{ $p }{ $r };
3715759b3d2Safresh1	    printf "static $type ${head}_%02x_%02x [256] = {\n", $p, $r;
3725759b3d2Safresh1	    for (my $c = 0; $c < 256; $c++) {
3735759b3d2Safresh1		print "\t", defined $val{$p}{$r}{$c}
3745759b3d2Safresh1		    ? "($type)".$val{$p}{$r}{$c}
3755759b3d2Safresh1		    : $null;
3765759b3d2Safresh1		print ','  if $c != 255;
3775759b3d2Safresh1		print "\n" if $c % 8 == 7;
3785759b3d2Safresh1	    }
3795759b3d2Safresh1	    print "};\n\n";
3805759b3d2Safresh1	}
3815759b3d2Safresh1    }
3825759b3d2Safresh1    foreach my $p (sort { $a <=> $b } keys %val) {
3835759b3d2Safresh1	next if ! $val{ $p };
3845759b3d2Safresh1	printf "static $type* ${head}_%02x [256] = {\n", $p;
3855759b3d2Safresh1	for (my $r = 0; $r < 256; $r++) {
3865759b3d2Safresh1	    print $val{ $p }{ $r }
3875759b3d2Safresh1		? sprintf("${head}_%02x_%02x", $p, $r)
3885759b3d2Safresh1		: "NULL";
3895759b3d2Safresh1	    print ','  if $r != 255;
3905759b3d2Safresh1	    print "\n" if $val{ $p }{ $r } || ($r+1) % 8 == 0;
3915759b3d2Safresh1	}
3925759b3d2Safresh1	print "};\n\n";
3935759b3d2Safresh1    }
3945759b3d2Safresh1    print "static $type** $head [] = {\n";
3955759b3d2Safresh1    for (my $p = 0; $p <= 0x10; $p++) {
3965759b3d2Safresh1	print $val{ $p } ? sprintf("${head}_%02x", $p) : "NULL";
3975759b3d2Safresh1	print ','  if $p != 0x10;
3985759b3d2Safresh1	print "\n";
3995759b3d2Safresh1    }
4005759b3d2Safresh1    print "};\n\n";
4015759b3d2Safresh1    close FH;
4025759b3d2Safresh1}
4035759b3d2Safresh1
4045759b3d2Safresh1}   # End of block for SelectSaver
4055759b3d2Safresh1
4065759b3d2Safresh11;
4075759b3d2Safresh1__END__
408