package RISCOS::SWI;

require Exporter;
use Carp;
use strict;

use vars qw ($VERSION @ISA @EXPORT @EXPORT_OK @in @out $string2num $num2string
	     $num2string_mask $string2num_mask);
$VERSION = 0.07;
@ISA = qw(Exporter);
@EXPORT = qw(swi swix kernelswi regmask SWINumberFromString SWINumberToString);
@EXPORT_OK = qw(&V_Flag &C_Flag &Z_Flag &N_Flag &XOS_Bit);

# 0.04
# Tidied up error handling on SWINumberToString (ie it can now return undef)
# 0.05
# Prototypes
# Can now return register 15 (but not 'PC' - hmm).
# Can do block
# 0.06
# Added SWI number in hex for SWINumberToString if SWI is 'User' or 'XUser'
# and decode on OS_WriteI
# 0.07
# foreach rather than shift in regmask

for my $i (0..9) { $in[$i] = 1<<$i; $out[$i] = 1<<(31-$i); }
$out[15] = 1 << 21;	# PC out is hardcoded

sub V_Flag ()	{return 1 << 28;}
sub C_Flag ()	{return 1 << 29;}
sub Z_Flag ()	{return 1 << 30;}
sub N_Flag ()	{return 1 << 31;}
sub XOS_Bit ()	{return 1 << 17;}

sub regmask {
	my $ir = shift;
	my $or = shift;
	my $block = shift;
	my $mask = 0;
	if (defined $ir) { foreach (@$ir) { $mask |= $in[$_]; } }
	if (defined $or) { foreach (@$or) { $mask |= $out[$_]; } }
	if (defined $block)
	{
	    if ($block & 0xFFFFFFF0) {
		carp "Block register $block out of range 0-15" if $^W;
		$block &= 0xF;
	    }
	    $mask |= 1 << 11;
	    $mask |= $block << 12;
	}
	$mask;
}

$string2num_mask = regmask ([1]);
$_ = 'XOS_SWINumberFromString';		# Can't pass in a string constant
$string2num =  swix ('XOS_SWINumberFromString', $string2num_mask, $_);

sub SWINumberFromString ($) {
    my $name = shift;
    if ($name =~ /^(X?)OS_WriteI\+(.+)/) {
	my $base = $1 ? (1 << 17 + 256) : 256;
	my $num = $2;
	return $base + $num unless $num =~ /^"(.)"/;
	return $base + ord $1;
    } elsif ($name =~ /^X?User \((.+)\)$/) {
	my $num = $2;
	return ($num =~ /^&(.+)/) ? oct "0x$1" : $num;
    }

    RISCOS::SWI::swix(57, $string2num_mask, $name);
}

$num2string = SWINumberFromString ('XOS_SWINumberToString');
$num2string_mask = regmask([0..2],[2]);

sub SWINumberToString ($) {
    return undef unless defined (my $num = shift);
    my $len = 'xxxx';
    my $buffer = ' ' x 255;

    return undef
      unless defined RISCOS::SWI::swix($num2string, $num2string_mask, 0+$num,
				       $buffer, 255, $len);

    $len = unpack('i', $len) - 1;	# Interpret the result as an integer
    $buffer = substr($buffer, 0, $len);
    $buffer = sprintf "$buffer (&%06X)", $num if ($buffer =~ /^X?User$/);
    $buffer;
}

sub swi {
    my $result = &swix;
    croak (sprintf "Unexpected OS error number &%X: $^E", $^E)
      unless defined $result;
    0 + $result;			# To be consistent with perl5.001
}

$num2string && $string2num;	# True if we got the SWIs

__END__

=head1 NAME

RISCOS::SWI -- perl interface to SWI calls

=head1 SYNOPSIS

    use RISCOS::SWI;
    $number = RISCOS::SWI::swix ('OS_SWINumberFromString', regmask([1]), $name);
    @regs = unpack 'I10', kernelswi ('OS_File', 5, $filename);

=head1 DESCRIPTION

This module provides a SWI interface for perl. There are two alternative
interfaces supported - B<kernelswi> and B<swi>/B<swix>. Both take the SWI to
call as the first parameter, which can be specified by name or number. Although
calling a SWI by name makes for highly readable code, the name lookup itself
often takes longer than the actual SWI, so for production code it is wise to
perform the name lookup once at initialisation using C<SWINumberFromString> and
cache the number in a variable.

C<kernelswi> and C<swix> both automatically call the C<X> (error returning)
version of the SWI, return undefined on error and copy the error block number
and message to C<$^E>. C<swi> calls C<swix>, but will terminate the script with
the error number and message if an error occurs.

For both interfaces registers are initialised from perl variables according to
the following rules:

=over 4

=item the undefined value is passed as zero

=item "numbers" are passed as integers

=item "strings" are passed as pointers to the strings - perl automatically adds
a C<"\0"> at the end to create null terminated strings.

Overwriting the contents alters the variable's value - it is up to the script to
ensure that the perl scalar value is made long enough before calling the SWI.
Note also that B<string constants> are treated as B<read only> so attempting to
call

    $number = RISCOS::SWI::swix ('OS_SWINumberFromString', regmask([1]),
				 'OS_SWINumberFromString');

would cause a fatal runtime error.

=back

"strings" and "numbers" are in quotes because the internals rely on perl's flags
to determine whether a scalar is a number or string. The trouble comes when perl
has been implicitly converting between the two and thinks that the result of
S<C<6*7> is C<"42">>, which it will try to pass in as a pointer to a string.
The work around is to add zero to parameters that must be numeric, and
concatenate C<''> to parameters that are strings:

    $number += 0;
    
    $string = "0";	# This may be interpreted as the number zero
    kernelswi ($swi, 0, $string . '')		# Not now.

The latter is, to quote Paul Moore, "fairly obscure magic (deliberately
invalidating the flag which says that the string has a valid numeric value, and
then using the string before perl has a chance to notice that the numeric value
is still OK), but works fine."

The two interfaces are both built into the perl binary and so are always
available, with or without this module. They differ in the method of passing in
and returning results from registers.

=over 4 

=item kernelswi <name>|<number>, [<R0 value>, [<R1 value], ...

is similar to the C library function of the same name. It takes as parameters
the SWI to call and optionally up to 10 more values assigned to C<R0> - C<R9>
in order. Unassigned registers have undefined values (B<not> zero, unlike
C<BASIC>). If the SWI generates an error then undefined is returned, and C<$^E>
is set to the error number and message from the error block (I<c.f.> C<$!>). If
there is no error then C<kernelswi> returning a single scalar block of length
40, the packed return results from C<R0> - C<R9>. For example, these may be
converted to an array of integers with code of the form

    @regs = unpack 'I10', $kernelswi_result;

=item B<swix> <name>|<number>, [<mask>, [<value> ...

is similar to the alternative C veneer written by Edward Nevill and Jonathan
Roach and supplied with Acorn C versions 4 and later.

Like C<kernelswi>, S<swix> returns undefined and sets C<$^E> if an error is
generated. If there is no error, S<swix> returns the contents of C<R0>

I<mask> is a bitmask that describes the interpretation to place on the remaining
parameters. If it is omitted it is treated as zero (no parameters). Otherwise it
is best generated by the C<regmask> function. I<mask> B<must> be numeric -
string values are reserved and cause a fatal error at runtime.

=over 4

=item regmask <in>, [<out>, [<block>]]

I<in> and I<out> are references to arrays of register numbers to respectively
pass B<in>to and B<out> from the SWI. If either is undefined it is treated as
an empty array. Registers 0 to 9 can be passed in, 0 to 9 and 15 returned.

If present, I<block> is the number of register to set up to point to any
remaining parameters "left over". This provides a convenient way of generating
parameter blocks for SWIs such as C<Wimp_CreateWindow>.

=back

values follow in register number order - first values to pass in, then scalars
in which the value of registers out are returned. The script must ensure that
these scalars are at least 4 bytes long, as the assembler C<swix> veneer makes
no checks.

Although this interface seems considerably more complex than C<kernelswi>, it
does allow much greater flexibility in exactly which registers are wanted.

Integer results can be retrieved with code such as

    unpack('i', $len)

string results by dereferencing pointers

    unpack('p', $addr)

=item swi

calls C<swix>, returning C<R0 + 0> to ensure a number, or C<die>s with the
numeric and string values of C<$^E> if there was an error.

=back

C<RISCOS::SWI> also provides conversion functions between SWI names and numbers,
and symbolic constants for the 4 ARM flags and the OS 'X' bit.

=over 4

=item V_Flag

=item C_Flag

=item Z_Flag

=item N_Flag

return the bit corresponding to the position of flag in the C<PC/PSR>.

=item XOS_Bit

returns 0x20000 - which when set marks the error returning form of a SWI.

=item SWINumberToString <SWI number>

converts a SWI number to a name using the SWI C<OS_SWINumberToString>.
Returns the name of the SWI, or undefined if there was an error. Unknown
SWI numbers which the SWI C<OS_SWINumberToString> converts to 'C<User>'
or 'C<XUser>' are returned as 'C<User (&C00FEE)>' or 'C<XUser (&0B100D)>'.

=item SWINumberFromString <SWI name>

provides a full inverse to C<SWINumberToString>. "User" SWIs described above
are recognised, as are C<OS_WriteI> variants. Other SWIs numbers are
converted using the SWI C<OS_SWINumberFromString>.

=back

=head1 BUGS

C<swix> doesn't automatically ensure that scalars for return values exist and
are long enough. Additionally the current mask system doesn't allow the script
to specify whether it wants a number, string or fixed length block to be
returned, and let the perl internals convert and assign the return values
automatically. String "masks" are reserved for this purpose.

=head1 AUTHOR

Nicholas Clark <F<nick@unfortu.net>>, based on the previous perl ports.

The C<swi> interface is C<syscall> from the perl 5.001 port. The C<kernelswi>
interface is C<syscall> from the perl 3 port.

=cut
