package RISCOS::AOF::Symbol;
use Carp;
use strict;
use vars qw (@ISA $VERSION);

$VERSION = 0.03;	# Now that I have access to AOF 3.11 info
# 0.03 uses map
@ISA = qw();

sub new ($$$$$) {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $self  = {};
    my ($name, $at, $value, $area, $stringtable) = @_;

    return undef unless defined ($self->{__NAME} = 
      RISCOS::AOF::aof_string_from_table ($stringtable, $name));
    
    $self->{__AT} = $at ||= 0;
    carp sprintf "AT field in " . $self->{__NAME} 
      . " with value %08X contains unknown bits", $at if (~0xB7F & $at);
    
    
    $self->{__AREA} = &RISCOS::AOF::aof_string_from_table ($stringtable, $area)
      unless ($at & 5 == 1);
        
    $self->{__VALUE} = $value
      if ($at & 0x41);	# Bit 0 or 6 set

    $self->{__SCOPE} = ('Unknown', 'static', 'extern', '')[$at & 3];

    my $misc = [];
    
    if ($at & 3 == 2) {
	push @$misc, 'case insensitive' if $at & 8;
	push @$misc, 'weak' if $at & 0x10;
	push @$misc, 'common area' if $at & 0x40;
    } elsif ($at & 3 == 3) {
	push @$misc, 'strong' if $at & 0x20;
    }
    
    push @$misc, 'code datum' if $at & 0x100;
    push @$misc, 'FP args in FP regs' if $at & 0x200;
    push @$misc, 'simple leaf function' if $at & 0x800;

    $self->{__MISC} = $misc if @$misc;
        
    bless ($self, $class);
}

sub Name {
    my $self = shift;
    $self->{'__NAME'};
}

sub Value {
    my $self = shift;
    return $self->{__VALUE} unless defined $self->{__AREA};
    "$self->{__VALUE} relative to area '$self->{__AREA}'";
}

sub Defined {
    my $self = shift;
    $self->{__AT} & 1;
}

sub Scope {
    my $self = shift;
    $self->{'__SCOPE'};
}

sub Misc {
    my $self = shift;
    @{$self->{'__MISC'}};
}

package RISCOS::AOF;
require RISCOS::Chunkfile;

require Exporter;
use Carp;
use strict;
use vars qw (@ISA $VERSION @EXPORT_OK);

$VERSION = 0.02;
@ISA = qw(Exporter RISCOS::Chunkfile);
@EXPORT_OK = qw(aof_symboltable aof_string_from_table);

sub new ($$) {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    
    my $self = $class->SUPER::new ($_[0]);
    
    foreach my $what (qw(OBJ_HEAD OBJ_AREA OBJ_IDFN OBJ_SYMT OBJ_STRT)) {
	if ($self->Multiple ($what)) {
	    carp "AOF file has '$what' chunks at positions "
		 . join (' ', @{$self->Lookup ($what)})
	      if $^W;
	    return undef;
	}
    }
    
    my $chunk = $self->Chunk ('OBJ_HEAD');
    
    unless (defined $chunk) {
	carp "AOF has no 'OBJ_HEAD'" if $^W;
	return undef;
    }
    
    unless ($chunk->Length() >= 12) {
	carp "AOF header is far too short" if $^W;
	return undef;
    }
    my ($type, $version, $num_area, $num_sym, $entry_area, $entry_offset)
      = unpack 'I6', (my $head_data = $chunk->Data());
    
    if ($type != 0xC5E2D080) {
	if ($^W) {
	    if ($type == 0xC5E2D081) {
		carp 'Don\'t understand AOF Image type 1';
	    } elsif ($type == 0xC5E2D083) {
		carp 'Don\'t understand AOF Image type 2';
	    } elsif ($type == 0xC5E2D087) {
		carp 'Don\'t understand AOF Image type 3';
	    } else {
		carp sprintf "Don't understand unknown AOF type &%8X", $type;
	    }
	}
	return undef;
    }
    
    unless ($chunk->Length() == 24 + 20 * $num_area) {
	carp "AOF header reports $num_area area(s) - expect length to be "
	     . (24 + 20 * $num_sym) . ' bytes, actually ' . $chunk->Length()
	     . 'bytes ' if $^W;
	return undef;
    }
    
    $self->{'VERSION'} = sprintf "%2f", $version / 100;
    if ($entry_area) {
	$self->{'ENTRY_AREA'} = $entry_area - 1;
	$self->{'ENTRY_OFFSET'} = $entry_offset;
    }
    
    if (defined ($chunk = $self->Chunk ('OBJ_SYMT'))) {
	$self->{'SYMBOLS'}
	  = aof_symboltable ($chunk, $self->Chunk ('OBJ_STRT'))
    }
    
    $chunk = $self->Chunk ('OBJ_AREA');
    
    unless (defined $chunk) {
	carp "AOF has no 'OBJ_AREA'" if $^W;
	return undef;
    }
    
    $self->{'AREAS'} = [];
    
    my $offset = 24;
    
    while ($num_area--) {
	# Eventually this needs to be "new" object consisting of
	# name (will need symtable in place already)
	# ALignment
	# ATtributes
	# (Size) from Data - hmm - what to do about zero init - use undef?
	# (Number of relocations) - from Data
	# Base address
        
	push @{$self->{'AREAS'}}, substr $head_data, $offset, 20;
	$offset += 20;
    }
    
    $self;
}

sub String ($;@) {
    my $self = shift;
    my $stringtable = \($self->Chunk ('OBJ_STRT')->Data());
    return undef unless $stringtable and defined $_[0];

    return aof_string_from_table ($stringtable, $_[0]) unless wantarray;
    map  { aof_string_from_table ($stringtable, $_) } @_;
}

sub Creator ($) {
    my $self = shift;
    # Strip the trailing nulls
    ($self->Chunk ('OBJ_IDFN')->Data()) =~ /^([^\0]+)\0/s;
    
    $0;
}

sub Version ($) {
    my $self = shift;
    $self->{'VERSION'};
}

sub Symbols ($) {
    my $self = shift;
    $self->{'SYMBOLS'};
}

#
# Subroutines from the pre-OO version.
#
sub aof_stringtable ($) {
    return undef unless defined (my $chunk = shift);

    # And the PRM says
    # "The length stored at the start of the string table itself is identically
    #  the length stored in the OBJ_STRT chunk header."
    # And "ARM AOF Macro Assembler 3.06 (Acorn Computers Ltd)" stores
    # 168 in the chunk header, but 166 at the start of the table
    # (rink's o.rink_rtsys)
    # Nice one Acorn
    # So we will round them up to a multiple of 4
    
    unless (((3 + unpack ('V', $chunk)) & ~3) == ((3 + length $chunk) & ~3)) {
	carp sprintf "Stringtable reports length as %d, actually %d",
		     unpack ('V', $chunk),length $chunk
	  if $^W;
	return undef;
    }

    my $entries = {};
    $chunk = substr $chunk, 4;
    my $pos = 4;

    while ($chunk =~ /([^\0]+)/s) {
	$entries->{$pos} = $1;
	$pos += 1 + length $1;
	$chunk = substr $chunk, 1 + length $1;
    }

    $entries;
}

# Because strictly some bugger can write a stringtable with
# "....realloc\0" where 'alloc' is offset 6 and 'realloc' is offset 4
sub aof_string_from_table ($$) {
    my ($ref, $offset) = @_;
    return $ref->{$offset} if ref $ref eq 'HASH';
    
    return undef unless ref $ref eq 'LVALUE' or ref $ref eq 'SCALAR';
    
    (substr $$ref, $offset) =~ /^([^\0]+)\0/s;
    
    $1;
}

sub aof_symboltable ($$) {
    return undef unless defined (my $chunk = shift);


    my ($stringtable) = @_;
    my $symboltable;

    unless (ref $stringtable) {
	carp "aof_symboltable not passed a reference" if $^W;
	return ();
    }

    if (ref $stringtable eq 'RISCOS::Chunk') {
	if ($stringtable->ID ne 'OBJ_STRT') {
	    carp "aof_symboltable passed a reference to '" . $stringtable->ID
	      . "', not a stringtable" if $^W;
	    return ();
	}
	$stringtable = \$stringtable->Data;	# Ref to scalar data
    }
    elsif (ref $stringtable ne 'HASH') {
	carp "aof_symboltable not passed a recognised reference" if $^W;
	return ();
    }
    
    if (ref $chunk eq 'RISCOS::Chunk') {
	if ($chunk->ID ne 'OBJ_SYMT') {
	    carp "aof_symboltable passed a reference to '" . $chunk->ID
	      . "', not a symboltable" if $^W;
	    return ();
	}
	$chunk = $chunk->Data;
    }

    if (0xF & length $chunk) {
	carp "symbol table length " . length $chunk 
	  . " is not a multiplte of 16" if $^W;
	return ();
    }
    my $result = [];
    while ($chunk =~ s/(.{16})//s) {
	my $symbol = new RISCOS::AOF::Symbol unpack ('V4', $1), $stringtable;
	push @$result, $symbol if defined $symbol;
    }
    
    $result;
}
1;
__END__

=head1 NAME

RISCOS::AOF -- manipulate ARM Object Format files

=head1 SYNOPSIS

    use RISCOS::AOF;
    
    my $obj_file = new RISCOS::AOF $file;
    foreach my $symbol (@{$obj_file->Symbols}) {
	print $symbol->Name(), "\n" if $symbol->Scope eq 'extern';
    }

=head1 DESCRIPTION

C<RISCOS::AOF> provides a class derived from C<RISCOS::Chunkfile> to manipulate
the contents of B<A>RM B<O>ject B<F>ormat files. It provides a class
C<RISCOS::AOF::Symbol> to store details of each symbol in an C<AOF> file.

Currently the implementation is incomplete - only methods to manipulate the
symbol table have been written. Methods to manipulate area data are currently
unimplemented.

=head2 Methods

=over 4

=item new <array_ref>

=item new <file>

If passed a reference to an array it is assumed to be an array of
C<RISCOS::Chunk> objects to use as the file contents. Otherwise calls
C<RISCOS::File::load> to loads the file specified using and checks that it is
C<AOF>. Hence I<file> can be a filename, a reference to a filehandle, or a
reference to a scalar which is used as the file's contents.

If passed an array reference then this is used internally in the object, so
should be created with the anonymous array constructor C<[]> rather than a
reference to a named array variable (see  L<perldsc/Common Mistakes>).

Returns undefined if there was an error, or the file contents are corrupt.

=item String <offset> [...]

Looks up strings in the stringtable.

In scalar context returns the string at the first offset given. In array context
returns the list of strings referred to by the supplied list of offsets.

=item Creator

Returns whatever string is stored in the C<OBJ_IDFN> chunk - typically a
string identifying the creator of the C<AOF> file.
    
=item Version

Returns the version number from the C<AOF> file.

=item Symbols

Returns a reference to the array of C<RISCOS::AOF::Symbol> objects describing
the symbols in the C<AOF> file.

=back

=head1 RISCOS::AOF::Symbol

The C<RISCOS::AOF::Symbol> class is used to hold information about a symbol in
an C<AOF> file. The class provides the following methods to access this
information:

=over 4

=item Name

Returns the symbol name.

=item Value

Returns the symbol value. If the symbol is absolute then this is just a number.
Otherwise it is a string of the form "C<I<value> relative to area 'I<area>'>".

=item Defined

Returns true if the symbol is defined in this C<AOF> file. Returns false if it
is defined externally.

=item Scope

Returns the scope of the symbol ('C<static>', 'C<extern>' or '').

=item Misc

Returns an array of strings describing other properties of the symbol - I<e.g.>
'C<case insensitive>', 'C<weak>', 'C<strong>', 'C<common area>'.

=back

=head1 BUGS

As noted, methods to manipulate areas are currently unimplemented.

=head1 AUTHOR

Nicholas Clark <F<nick@unfortu.net>>
