# COBOLIO.pm
#
# Copyright (c) 2002 Harry Holt <hholt@comcast.net>.  All rights reserved.
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#
#  THIS PROGRAM IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
#  EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
#  AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, 
#  YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
#
# Reading and interpretation of COBOL copylibs into perl data structures
#
# All variable names are the same as the cobol names, but with the '-'
# changed to a '_' to avoid operator/keyword issues.
#
# SIGNS: Signs may be added to a COBOL pic clause and require special
#	processing (all OVER the place!!), so each name is given a SIGN attribute.  
#	The possible values are:
#		R - No PIC clause for this var (a record-level variable)
#		X - No SIGN specified, non-numeric
#		9 - No SIGN specified, but variable is numeric
#		+ - SIGN is specified.
#		C - Comp-3 (packed decimal), no sign.
#		3 - Comp-3 (packed decimal), WITH sign.
#	When the sign is specified, reading the data will require bit-shifting.
#	The LAST digit of the number is the sign and the last number.  If we
#	were still working in EBCDIC, would could do a straight bit-shift and
#	get the sign and the number, but because of the ASCII translation, things
#	don't work out like they should:
#		A-I = + 1-9
#		{	= + 0
#		J-R = - 1-9
#	I'm not sure what to look for if the value was a 0 with a negative sign.
#	It SHOULD be Hex D0, but since this has no representation in EBCDIC, I
#	don't know how it's represented.  +0 should be Hex C0, but it is 
#	represented as { anyway.
#	To make things simple for the Perl programmer, we will handle all the translations,
#	and often add an extra byte to the numeric variable to allow for the "-" sign.
#	Then we strip in off to update the variable value (THIS NEEDS REFACTORING).
#
# DECIMALS:  There may be a "V" in the PIC clause.  If so, this value will be greater 
#    than 0, specifying the number of "9"s after the "V" (to the right of the decimal)
#    
#*****************************************************************************************
#* Version 0.1		11-16-2002
#* Version 0.2		05-05-2004	Bug fixes and contributions by Steve Tolkin (Steve.Tolkin@FMR.COM)
#*
#*****************************************************************************************
use strict;
package COBOLIO;
#
#
#
require Exporter;
require DynaLoader;
#
our $COBOLIOPackage = "COBOLIO";
#
our @ISA = qw( Exporter DynaLoader );
#
#
#
our $FD;
our %FD;
our $FDVals;
our %FDVals;
our $self;
our $glFDRec;
our $glFDL;
#
#
sub new {
	(my $class, my $copyLibName, my $fdRec, my $isFD, my $rec01) = @_;
	
	my $recList = {};
	#$FD{$fdRec} = $recList;
	$FD{$fdRec} = CreateCobolRec( $copyLibName, $fdRec, $isFD, $rec01);
	$FDVals{$fdRec}->{VAL} = "";

#	$self = { $class, $FD, $FDVals };
	$self = { $class, $FD };
	bless $self;

return $self;
}

sub PrintLayouts {
	($self, my $FDName) = @_;

	# Show the layouts of the existing copylib members	
	my $filedesc;
	my $recList;
	for $glFDRec (keys %FD) {
		print "**********************  $glFDRec  **********************\n";
#		printf "%-28s  %7s  %7s  %7s  %6s  %7s \n", "Name", "Level", "Start", "Len", "Sign", "Parts";
                # Steven Tolkin changed 29 to 30 in next several lines.
                # Cobol allows 30 chars in a name, and without this
                # change the output report is not fixed width.
                # This matters because in some cases a value, e.g. for Start,
                # might be omitted.
		printf "%-30s %7s %7s %7s %6s %7s %5s \n", "Name", "Level", "Start", "Len", "Sign", "Decimals", "Parts";
		printf "%-30s %7s %7s %7s %6s %7s %5s \n", "-" x 29, "-----", "-----", "---", "----", "-----", "-----";
		for $recList (sort BySRT keys %{ $FD{$glFDRec} }) {
			printf "%-30s %7s %7s %7s %6s %7s %5s \n", 
				$recList, 
				$FD{$glFDRec}{$recList}->{LEVEL},
				$FD{$glFDRec}{$recList}->{STARTPOS},
				$FD{$glFDRec}{$recList}->{LEN},
				$FD{$glFDRec}{$recList}->{SIGN},
				$FD{$glFDRec}{$recList}->{DECIMALS},
				$FD{$glFDRec}{$recList}->{PARTS} ;
		}
		print "\n\n";
	}
}



sub GetRec {
	(my $self, my $fdRec) = @_;
	my $recList = $FD{$fdRec};
	return $recList;
}

#sub DESTROY {
#	my ($self) = shift;
#	if(!undef($self->{cbNames})) {
#		undef $self->{cbNames};
#	}
#	return 1;
#}

sub ReadRecInto {
	# Start by setting up the record structure
	($self, my $inputLine, my $fdRec) = @_;

	my $recList;
	my $recInLen = 80;
	my $this01Name;
	for $recList ( %{$FD{$fdRec} } ) {
		if(defined($FD{$fdRec}{$recList}->{REC01})) {
			$this01Name = $FD{$fdRec}{$recList}->{REC01};
			last();
		}
	}
	$recInLen = $FD{$fdRec}{$this01Name}->{LEN};
	
	$FDVals{$fdRec}->{VAL} = substr($inputLine.(' ' x $recInLen),0,$recInLen);
	return 1;
} #### End sub ReadRecInto


sub GetVal {
	($self, my $dataItemName, my $fd, my $dataRecName) = @_;

	my $fdRec;
	my $actualLen;
	my $actualPos;
	my $retVal;

	# If the item has an "Occurs" clause, we may need a subscript
	# of the value, so we need to parse the name to get the name and subscript
	($dataItemName, my $subScr, my $post) = split /[(,)]/, $dataItemName;
	if(defined($subScr)) {
		if($subScr < 1) { $subScr = 1; }
	} else {
		$subScr = 1;
	}

	# If the FD is not passed, we will look it up
	if(defined($fd)) {
		$fdRec = $fd;
	} else {
		$fdRec = FindFDForRecord($dataItemName);
	}

	if(!(defined($dataRecName))) {
		$dataRecName = $FD{$fdRec}{$dataItemName}->{REC01};
	}

	# Get the part of the major record that contains the record being asked for.
	if(defined($FD{$fdRec}{$dataItemName}->{PARTS})) {
		if($FD{$fdRec}{$dataItemName}->{PARTS} > 0) {
			$actualLen = $FD{$fdRec}{$dataItemName}->{LEN} / $FD{$fdRec}{$dataItemName}->{PARTS};
		} else {
			$actualLen = $FD{$fdRec}{$dataItemName}->{LEN}
		}
	} else {
		$actualLen = $FD{$fdRec}{$dataItemName}->{LEN}
	}
	$actualPos = $FD{$fdRec}{$dataItemName}->{STARTPOS} + ($actualLen * ($subScr - 1));
	$retVal = substr( $FDVals{$fdRec}->{VAL}, $actualPos, $actualLen);


	if($FD{$fdRec}{$dataItemName}->{SIGN} eq "9") { 
		$retVal =~ s/ /0/g; 
		$retVal =~ s/\x00/0/g;
	}

	# Deal with an signed numeric data
	if($FD{$fdRec}{$dataItemName}->{SIGN} eq "+") {
		$retVal =~ s/ /0/g; 
		$retVal =~ s/\x00/0/g;
		if(substr($retVal,$actualLen - 1,1) eq "{") {
			$retVal = substr($retVal,0,$actualLen - 1)."0";
		} else {
			if(substr($retVal,$actualLen - 1,1) eq "}") {
				$retVal = '-'.substr($retVal,0,$actualLen - 1)."0";
			} else {
				if($retVal =~ m/[A-I]/) {
					$retVal =~ tr/[ABCDEFGHI]/[123456789]/;
				}
				if($retVal =~ m/[J-R]/) {
					$retVal =~ tr/[JKLMNOPQR]/[123456789]/;
					$retVal = '-'.$retVal;
				}
			}
		}
	} 	# End of SIGN logic

	# If decimals are specified, we will need to add a "." in the right spot
	if($FD{$fdRec}{$dataItemName}->{DECIMALS} > 0) {
		$retVal = substr($retVal,0,length($retVal) - $FD{$fdRec}{$dataItemName}->{DECIMALS}).
				".".substr($retVal,length($retVal) - $FD{$fdRec}{$dataItemName}->{DECIMALS});
	}	# End of DECIMAL logic

	return $retVal;
}
	

sub GetCSVRecord {
	($self, my $fd, my $name01) = @_;
	#********
	# We are asked to Comma-separate the entire record and return it in that
	# format.  The loop below will roll through each entry at the field record,
	# sorted in natural (copylib) order.  A specific "01 Level" record name 
	# can be specified, so we make sure that we have found that record before
	# pulling values.  The '$in01' variable is a flag that tells us if we are
	# in the right record.
	#
	# We don't pull values for record-level field names, but we may need to pull
	# the values within a record several times if the record contains an "OCCURS"
	# clause.  The value of the OCCURS is stored in the ->{PARTS} item, so we
	# use that to create extra loops for records or data items.
	my $filedesc;
	my $reclist;
	my $fdRec = $fd;
	my $outRec = "";
	my $flNeedSep = 0;
	my $in01 = 0;
	my $i = 1;
	my $inRR = 0;
	my $RRLevel = "00";
	my $RRcnt = 0;
	my @RRRecs;
	my $RRParts = 1;
	my $currRRRec;

	if(!(defined($name01))) { $in01 = 1; $name01 = ""; }

	for $filedesc (keys %FD) {
		if($filedesc eq $fdRec) {
			$glFDRec = $filedesc;
			for $reclist (sort BySRT keys %{ $FD{$filedesc} }) {
				if($reclist eq $name01) { $in01 = 1; }
				if($in01 == 1) {
					if($flNeedSep == 1) {
						$outRec .= ",";
						$flNeedSep = 0;
					}
#					if($inRR == 1) {
#						if($FD{$filedesc}{$reclist}->{LEVEL} le $RRLevel) {
#							$inRR = 0;
#							for($i = 2; $i <= $RRParts; $i++) {
#								foreach $currRRRec (@RRRecs) {
#									$outRec .= "\"".GetVal("",$currRRRec)."\",";
#								}
#							}
#						} else {
#							$RRRecs[$RRcnt] = $reclist;
#							$RRcnt++;
#						}
#					}
							
#					if( ($FD{$filedesc}{$reclist}->{SIGN} eq "R") && ($FD{$filedesc}{$reclist}->{PARTS} > 1) ) {
#						$inRR = 1;
#						$RRLevel = $FD{$filedesc}{$reclist}->{LEVEL};
#						$RRParts = $FD{$filedesc}{$reclist}->{PARTS};
#					}
					if($FD{$filedesc}{$reclist}->{SIGN} ne "R") {
						if($FD{$filedesc}{$reclist}->{PARTS} > 1) {
							for($i = 1; $i <= $FD{$filedesc}{$reclist}->{PARTS}; $i++) {
								if($flNeedSep == 1) {
									$outRec .= ",";
									$flNeedSep = 0;
								}
								$outRec .= "\"".GetVal("",$reclist."(".$i.")")."\"";
								$flNeedSep = 1;
							}
						} else {
							$outRec .= "\"".GetVal("", $reclist)."\"";
							$flNeedSep = 1;
						}
					}
				}
			}
		}
	}	
	return $outRec;
}

sub GetFFRecord {
	($self, my $fd, my $name01) = @_;
	
	my $filedesc;
	my $reclist;
	my $curGotVal;
	my $outLen = 0;
	my $fdRec = $fd;
	my $outRec = "";
	my $in01 = 0;
	if(!(defined($name01))) { $in01 = 1; }
	
	for $filedesc (keys %FD) {
		if($filedesc eq $fdRec) {
			$glFDRec = $filedesc;
			for $reclist (sort BySRT keys %{ $FD{$filedesc} }) {
				if($reclist eq $name01) { $in01 = 1; }
				if($in01 == 1) {
					$outLen = $FD{$filedesc}{$reclist}->{LEN};
					$curGotVal = GetVal("", $reclist);
					if(substr($curGotVal,0,0) eq "-") { 
						$outLen += 1; 
					}
					if($curGotVal =~ m/\./g) { 
						$outLen += 1; 
					}
					$curGotVal .= " " x $outLen;
					$outRec .= substr($curGotVal,0,$outLen);
				}
			}
		}
	}
	
	return $outRec;
}

sub GetCSVHeader {
	($self, my $fd, my $name01) = @_;
	
	my $filedesc;
	my $reclist;
	my $fdRec = $fd;
	my $outRec = "";
	my $flNeedSep = 0;
	my $in01 = 0;
	if(!(defined($name01))) { $in01 = 1; $name01 = ""; }
	
	for $filedesc (keys %FD) {
		if($filedesc eq $fdRec) {
			$glFDRec = $filedesc;
			for $reclist (sort BySRT keys %{ $FD{$filedesc} }) {
				if($reclist eq $name01) { $in01 = 1; }
				if($in01 == 1) {
					if($flNeedSep == 1) {
						$outRec .= ", ";
						$flNeedSep = 0;
					}
					if($FD{$filedesc}{$reclist}->{SIGN} ne "R") {
						$outRec .= $reclist;
						$flNeedSep = 1;
					}
				}
			}
		}
	}
	
	return $outRec;
}
	
sub SetVal {
	($self,	my $dataItemName, my $newValue, my $fd, my $dataRecName) = @_;

	my $fdRec;
	my $dataLen;
	my $strMask;
	my $packTempl;
	
	if(defined($fd)) {
		$fdRec = $fd;
	} else {
		$fdRec = FindFDForRecord($dataItemName);
	}
	if(length($dataRecName) < 1) {
		$dataRecName = $FD{$fdRec}{$dataItemName}->{REC01};
	}

	if((substr($newValue,0,5)) eq "SPACE") {
		$newValue = " " x $FD{$fdRec}{$dataItemName}->{LEN};
	}
	
	# Fix any numerics.  Allow an extra space in case of a sign
	if($FD{$fdRec}{$dataItemName}->{SIGN} =~ m/\+|9/) {
		$dataLen = $FD{$fdRec}{$dataItemName}->{LEN} + 1;
		$strMask = 'sprintf("%0'.$dataLen.'d", $newValue);';
		$newValue = eval($strMask);
	}
	
	# We need to deal with Signed numerics somehow.  This logic seems easily breakable, though
	if($FD{$fdRec}{$dataItemName}->{SIGN} eq "+") {
		if(substr($newValue,0,1) eq "+") { $newValue = substr($newValue,1); }
		if(substr($newValue,0,1) eq "-") {
			for($newValue) {
				s/0\z/\}/g;
				s/1\z/J/g;
				s/2\z/K/g;
				s/3\z/L/g;
				s/4\z/M/g;
				s/5\z/N/g;
				s/6\z/O/g;
				s/7\z/P/g;
				s/8\z/Q/g;
				s/9\z/R/g;
			}

		} else {
			for($newValue) {
				s/0\z/\{/g;
				s/1\z/A/g;
				s/2\z/B/g;
				s/3\z/C/g;
				s/4\z/D/g;
				s/5\z/E/g;
				s/6\z/F/g;
				s/7\z/G/g;
				s/8\z/H/g;
				s/9\z/I/g;
			}
		}
	}	# Finished dealing with signed numerics ###################################
	
	# Get rid of extra place for sign
	if(substr($newValue,0,1) eq "-") { $newValue = substr($newValue,1); }

	$newValue =~ s/'.'//g;
	
	$packTempl = 'A'.$FD{$fdRec}{$dataItemName}->{LEN};
	$newValue = pack($packTempl,$newValue);

	# In case the VAL is not large enough for the data item, add enough spaces
	if(length($FDVals{$fdRec}->{VAL}) < $FD{$fdRec}{$dataItemName}->{STARTPOS}) {
		$FDVals{$fdRec}->{VAL} .= " " x $FD{$fdRec}{$dataItemName}->{STARTPOS};
	}

	# Set the actual value within the larger record
	$FDVals{$fdRec}->{VAL} = 
		substr($FDVals{$fdRec}->{VAL},0,$FD{$fdRec}{$dataItemName}->{STARTPOS}).
		$newValue.
		substr($FDVals{$fdRec}->{VAL},$FD{$fdRec}{$dataItemName}->{STARTPOS} +
							$FD{$fdRec}{$dataItemName}->{LEN});
	return 1;
}
			
sub CreateCobolRec {
	(my $copyLibName, my $fdRec, my $isFD, my $rec01) = @_;

	#################################################################################
	# Here we create the "Data Division" interpretations for the 
	# perl variables.  Each name found in the COBOL code becomes a 
	# data element name with the following attributes:  
	#	
	#	LEVEL - The COBOL "record level", 01, 03, 05, 88, etc.
	#	STARTPOS - The starting position of the data element within the overall record
	#	LEN - The character length of the data element
	#	SIGN - Whether a +/- sign is used for a numeric value
	#	REC01 - The top-level (01) data element name that this element is in.
	#
	# The File Descriptor (FD) of the COBOL source also contains a VAL attribute,
	# which holds the value of a single record when it is read in.  To 
	# find or set the value of any other record or data element, the 
	# appropriate section of the complete record is used.
	#
	# To accomodate different styles of using COBOL "copy" statements, the initial
	# 01 level record can either be included in the copy member file, or it can
	# be supplied on the command line.
	# NOTE: COBOL "01" record-level intries subordinate to an "FD" clause are 
	#		implicit "REDEFINES".
	##################################################################################
	### Initializations
	my $periodAt;
	my $nextPos = 0;
	my $currLevelLen = 0;
	my $currentLevel = 01;
	my $fillerCount = 0;
	my $recStarted = 0;
	my $charCount = 0;
	my $currLevel = 01;
	my $cnt = 0;
	my $name;
	my $picChars = "";
	my $rec01Name;
	my $recName;
	my $level;
	my @recNames;
	my @recLevel;
	my @recLen;
	my @cplLines = ();
	my @cpl = ();
	my @vals = ();
	my $initVal = "";
	my $cplLine = "";
	my $rl;
	my $occurring = 0;
	my @occurringLevel;
	my @stuff;
	my $parts;
	my $picClause;
	my $recList;
	my @recParts;
	my $sign;
	my $other;
	my $decimals;
	my $filePos;
	my $rLevel;
	my $dataName;
	my $occFlag = 0;
	my $occLen = 0;
	my $occLevel = "01";
	my $occMult = 0;

	if(defined($rec01)) {
		$rec01Name = $rec01;
		push @cplLines, "       01  ".$rec01Name.'.';
	}

	#### Read the COPYLIB file
	die "I can't open the file $copyLibName because $!" unless
		open(COPYLIB, $copyLibName);
	while(<COPYLIB>) {
		push @cplLines, $_;
	}
	close(COPYLIB);

	#### Concatenate all COPYLIB lines into 1 line-per-sentence 
	for($cnt = 0; $cnt <= $#cplLines; $cnt++) {
		if(defined $cplLines[$cnt]) {
			while( (substr($cplLines[$cnt],6,1) eq "*") && ($cnt <= $#cplLines) ) { 
				$cnt++; 
			}  	# Ignore any starting comments
		}
		last if($cnt > $#cplLines);						# exit for if end of file
		$cplLine = substr($cplLines[$cnt],6,66);			# get rid of line #'s & comments
		if($cplLine =~ m/\S/) {							# skip all-whitespace (blank) lines
			while($cplLine !~ /\./) {					# concatenation based on the period
				if($cplLine =~ "\"") {
					$cplLines[$cnt + 1] =~ s/\"//;		# remove redundant quotes on next line
				}
				if(substr($cplLines[$cnt + 1],6,1) ne "*") {
					$cplLine .= substr($cplLines[$cnt + 1],11,61);
				}
				$cnt += 1;
			}
			if($cplLine =~ m/\./g) {			# chop line after the period
				$periodAt = pos($cplLine);
				$cplLine = substr($cplLine,0,$periodAt);
			}
			push @cpl, $cplLine;
		}
	}

	##########################################################################
	# Next starts the loop to interpret the COBOL data members and create the
	# hash of hashes to store the attributes of the data items.
	#
	##########################################################################
	my $cplSub = 0;
	for (@cpl) {
		@stuff = ();
		s/-/_/g;		# Change all "-" (dashes) to "_" (underscores)
		s/\.//g;		# Eliminate all periods
		if($_ =~ "PIC") {	# Get the position, length of defined data member
			@stuff = split;
			$level = $stuff[0];
			$name = $stuff[1];
			if($name eq "FILLER") { 	# Make "FILLER" fields have unique names
				$name = "FILLER".$fillerCount;
				$fillerCount++;
			}

			if(defined $stuff[2]) {
				if($stuff[2] =~ "OCCURS") { 	# Check for "OCCURS" clause
					$parts = $stuff[3];			# PARTS = 1 unless the "OCCURS"
					$picClause = $stuff[6];		# clause defines multiples.
				} else {
					$parts = 1;
					$picClause = $stuff[3];
				}
				if($stuff[2] =~ "REDEFINES")  {						# Check for a "REDEFINES" clause
					$nextPos = $recList->{$stuff[3]}->{STARTPOS};	# which will reset the $nextPos counter
					$picClause = $stuff[5];
				}
			}			
			if(substr($picClause,0,1) eq "S") { 	# Look for a signed numeric
				$sign = "+";
			} else {
				if(substr($picClause,0,1) eq "9") {
					$sign = "9";
				} else {
					$sign = "X";
				}
			}
			###########
			# Next, the PICTURE clause is parsed to determine the data type
			# and the size of the field.
                        
                        # Tolkin bug fix part 1.
                        # Added the third parm (LIMIT of 3) to split.
                        # Without this it used to produces $other of V9
                        # from 9(03)V9(02) which is wrong.
                        # After the fix it produces V9(02)
                        # Another way to solve this would set limit of 4 and
                        # get that last piece now, but we do it below.
			($picChars, $charCount, $other) = split /[(,)]/ ,$picClause, 3;
                        #warn "dbg 1 Tolkin:$picChars, $charCount, $other\n"; 
			if(!(defined $charCount)) { 
				($picChars, $other) = split /V/, $picChars;
				$picChars =~ s/S//g;
				$charCount = length($picChars);
				if(defined $other) { $charCount += length($other); }
			} else {
				$other =~ s/V//g
					if defined $other;
                                # Tolkin bug fix part 2 We change
                                # e.g. 9(02) into 99 using Perl
                                # operator x which "returns a string
                                # consisting of the left operand
                                # repeated the number of times
                                # specified by the right operand"
                                if (defined $other &&
                                    $other =~ m/^(.)\((\d+)\)$/) {
                                    $other = $1 x $2;
                                    #warn "dbg o Tolkin:$other\n";
                                }
                                # end fix part 2
				$charCount = $charCount + length($other);
				$charCount = $charCount * $parts;
			}
			$decimals = 0;
			$decimals = length($other)
				if defined $other;
                        #warn "dbg 2 Tolkin:$picChars, $charCount, $other\n";
                        #warn "dbg 3 Tolkin:$decimals\n"; 
			if(defined $stuff[4]) {									# Need to make adjustments 
				if($stuff[4] =~ "COMP_3") {							# to the length of 
					if($recList->{$name}->{SIGN} == "+") {			# the variable if 
						$recList->{$name}->{SIGN} = "3";			# it is defined as a 
					} else {										# COMP-3, which 
						$recList->{$name}->{SIGN} = "C";			# compresses the data
					}
					if($charCount % 2 > 0) {
						$charCount = (int ($charCount / 2)) + 1;
					} else {
						$charCount = $charCount / 2;
					}
				}
			}
				
			if($occFlag == 1) {
				if($level < $occLevel) {
					$nextPos += ($occLen  * ($occMult - 1));
					$occFlag = 0;
					$occLen = 0;
				}
			}
			$recList->{ $name } = { 			# Set all the hash values
				LEVEL => $level,				# for this variable
				STARTPOS => $nextPos,
				LEN => $charCount,
				SIGN => $sign,
				REC01 => $rec01Name,
				PARTS => $parts,
				DECIMALS => $decimals,
				SRT => $cplSub,
			};
			if(defined $stuff[4]) {
				if($stuff[4] =~ "VALUE") {
					@vals = split/VALUE/;
					$initVal = $vals[1];
					if($initVal =~ "SPACE") {
						$initVal = " " x $charCount;
					}
					if($initVal =~ "ZERO") {
						$initVal = "0" x $charCount;
					}
					$initVal =~ s/\s+//x;
					$initVal =~ s/\"//g;
					SetVal("", $name, $initVal, $fdRec);
				}
			}
			$nextPos += $charCount;
			
			while($currLevel > $level) {
				$recName = pop @recNames;
				$currLevel = pop @recLevel;
				pop @recLen;
				pop @recParts;
				
			}
			if($occFlag == 1) { $occLen += $charCount; }
		#
		##########  Record-level data variables are dealt with in the "else" clause ##############
		#
		} else {		# Deal with record-level data variables
			@stuff = split;
			$level = $stuff[0];
			if($level == 01) { 
				$rec01Name = $stuff[1];
				if($isFD == 1) { $nextPos = 0; }
			}
			if(!(defined($currLevel))) {
				$currLevel = "01";
			}
			while($currLevel > $level) {
				if($#recNames > -1) {
					$recName = pop @recNames;
					if(defined($recList->{$recName}->{LEN})) {
						$recList->{$recName}->{LEN} += (pop @recLen) * (pop @recParts);
					} else {
						$recList->{$recName}->{LEN} = (pop @recLen) * (pop @recParts);
					}
				}
				$currLevel = pop @recLevel;
			}
			if(defined $stuff[2]) {
				if($stuff[2] =~ "REDEFINES")  {
					$nextPos = $recList->{$stuff[3]}->{STARTPOS};
				}
				if($stuff[2] =~ "OCCURS") { 
					$parts = $stuff[3];
					$occFlag = 1;
					$occLevel = $level;
					$occMult = $parts;
				} else {
					$parts = 1;
					$occFlag = 0;
				}
			} else {
				$parts = 1;
				$occFlag = 0;
			}
			$recList->{$stuff[1]} = {
				LEVEL => $level,
				STARTPOS => $nextPos,
				LEN => 0,
				SIGN => "R",
				REC01 => $rec01Name,
				PARTS => $parts,
				DECIMALS => 0,
				SRT => $cplSub,
			};
			push @recNames, $stuff[1];
			push @recLevel, $level;
			push @recLen, 0;
			push @recParts, $parts;
		}
		$currLevel = $level;
		NEXTREC:
		$cplSub++;
	}

	while($recName = pop @recNames) {   # Get lengths for remaining levels.
		pop @recLevel;
		pop @recParts;
	}
	
	my @rns = ();
	$glFDL = $recList;
	for $recName (sort ByPreSRT keys %$recList) {
		if($recList->{$recName}->{SIGN} eq "R") {
			push @rns, $recName;
		}
	}
	for $recName (@rns) {
		$filePos = $recList->{$recName}->{STARTPOS};
		$rLevel = $recList->{$recName}->{LEVEL};
		$glFDL = $recList;
		for $dataName (sort ByPreSRT keys %$recList) {
			if($recList->{$dataName}->{SRT} > $recList->{$recName}->{SRT}) {
				if($rLevel < $recList->{$dataName}->{LEVEL}) {
					if($recList->{$dataName}->{SIGN} ne "R") {
						if($recList->{$dataName}->{STARTPOS} >= $filePos) {
							$recList->{$recName}->{LEN} += ($recList->{$dataName}->{LEN} * $recList->{$recName}->{PARTS});
							$filePos = $recList->{$dataName}->{STARTPOS} + $recList->{$dataName}->{LEN};
						} 
					}
				} else { 	# Exit loop if new record at the same level
					last; 
				}
			}
		}
	}
	return $recList;
}	# sub CreateCobolRec()


sub ByLevel { 
	my $fdRec;
	$FD{$fdRec}{$a}->{LEVEL} <=> $FD{$fdRec}{$b}->{LEVEL}; 
}

sub BySRT { 
		$FD{$glFDRec}{$a}->{SRT} <=> $FD{$glFDRec}{$b}->{SRT};
}

sub ByPreSRT {
	$glFDL->{$a}->{SRT} <=> $glFDL->{$b}->{SRT};
}

sub ByPosition {
	$FD{$glFDRec}{$a}->{STARTPOS} <=> $FD{$glFDRec}{$b}->{STARTPOS}
		||
	$FD{$glFDRec}{$a}->{LEVEL} <=> $FD{$glFDRec}{$b}->{LEVEL};
}

sub FindFDForRecord {
	my $recordName = shift;
	my $returnValue = "";
	my $foundCount = 0;
	my $dataName;
	my $filedesc;
	my $reclist;
	
	for $filedesc (keys %FD) {
		for $reclist (keys %{ $FD{$filedesc} }) {
			if($reclist eq $recordName) {
				$returnValue = $filedesc;
				$foundCount += 1;
##				print "Found $recordName in $filedesc\n";
##				return $returnValue;
			}
		}
	}
	if($foundCount > 1) {
		die "Ambiguous record name specified $recordName\n";
	}
	return $returnValue;
}

1;

