# fortseqfile.rb
# Copyright (C) by GFD-Dennou Club, 2002.  All rights reserved.

=begin
= class FortSeqFile
File を継承
* FORTRAN の順番探索(順編成)書式無しファイル
* ビッグエンディアンとリトルエンディアンに対応
* 日立の特殊なものには対応していない。
=end

module NumRu
  class FortSeqFile < File

    class FormatError < StandardError; end

    private

    def try_record(recl, packchar)
	@try = read(recl)
	new_recl = read(4).unpack(packchar).first
	if (new_recl == recl) then
	    @endian = packchar
	    true
	else
	    false
	end
    end

    def check_endian
	recl = read(4)
	le = recl.unpack('V').first
	be = recl.unpack('N').first
	if (le < be) then
	    return true if try_record(le, 'V')
	    @retry = true
	    return true if try_record(be, 'N')
	else
	    return true if try_record(be, 'N')
	    @retry = true
	    return true if try_record(le, 'V')
	end
	false
    end

    public

    def initialize (file, mode = 'r', endian = nil)
	super(file, mode)
	@try = nil
	@endian = endian
	@irec = 0
	@retry = false
	return if (not endian.nil?)
	raise FormatError unless check_endian
	begin
	    seek(0)
	    @try = nil
	rescue
	    raise FormatError, 'endian guess failed for non-seekable' if @retry
	end
    end

=begin
--- f.endian
    ビッグエンディアンの場合 'N' を、
    リトルエンディアンの場合 'V' を返す。
    これは unpack に使える。
--- f.irec
    記録番号を返す。ファイルを開いたばかりのとき 0。
=end

    attr_reader(:endian, :irec)

=begin
--- f.read_record
    レコードを一つ読み取る。
    ファイルエンドでは EOFError 例外を起こす。
--- f.get_record
    レコードを一つ読み取る。
    ファイルエンドで nil を返す。
=end

    def read_record
	if @try then
	    rec = @try
	    @try = nil
	    return rec
	end
        begin
	    recl = read(4).unpack(@endian).first
	    body = read(recl)
	    if (read(4).unpack(@endian).first != recl) then
		raise FormatError, "Bad record trailer"
	    end
	# ファイルエンドでは read が返した nil を unpack する
	rescue NameError => err
	    raise EOFError, 'END of file'
	end
	@irec += 1
	return body
    end

    def get_record
	begin
	    read_record
	rescue EOFError
            return nil
	end
    end

    def self::open (fnam, mode='r')
	fp = new(fnam, mode)
	if defined? yield
	    yield fp
	    fp.close
	else
	    fp
	end
    end

    def self::debug
	ARGV.each { |fnam|
	    fp = self::new(fnam, "r")
	    while r = fp.get_record
		if (r.size > 1000)
		    printf("size=%d %s...\n", r.size, r.inspect[0..100])
		else
		    p r
		end
	    end
	    fp.close
	}
    end

  end
end
