%endoflist  { do not output translated C code }
%control 1

! TO DO: see *CHANGED* *ADDED* etc for mods due to
! https://history.dcs.ed.ac.uk/archive/os/emas/emas2/compilers/imp80/doc/manual/imp80.html
! which have not yet been updated in perms.[hc]

! THIS FILE *MUST* BE KEPT IN SYNC WITH perms.h and perms.c

! These prims are a mixture of prims from multiple systems, in an attempt
! to be backward compatible with as many Imp environments as possible.
! Unfortunately a small number of library calls are incompatible between
! versions; specifically PRINT and PRINT FL which in one incarnation
! have one argument for places and in another have two.  It's marginally
! possible to support both using either stdargs, or the C11 support for 
! varadic macros.

! How it all works: this file does not cause any C code to be output - the
! C output only starts on %endofperm ... this exists only to get the declarations
! into the symbol table for use by the program that follows.  When the generated
! C is compiled, it includes perms.h instead, which affords us the opportunity
! to do some tricks with the C pre-processor etc which come in handy for
! handling signals for example.

! procedures declared as %perm or %prim are treated similarly to standard
! external routines, except that they are declared at an outer level of scope
! and can therefore be superceded by a user-written top-level procedure.

! %perm routines are just normal routines as far as the translation to C
! is concerned - but %prim routines are often implemented by code in the compiler
! itself and there may be no actual body for these procedures (although
! there *can* be, in addition to generated code). They can also be implemented
! by macro calls or C's "inline" procedures from within perm.h.

! Note also that %systemroutine procedures are *C* externals, not ones
! written in Imp - the primary difference (apart from trivial name issues)
! is that C procedures evaluate their parameters in right-to-left order
! as opposed to Imp which is defined as evaluating parameters from left-to-right.

! %external routines obey the system calling conventions and are exported from
! object files with the same names as declared in Imp.  %perm/%prim/%system
! declarations however - if they generate external linkage - modify the exported
! name so that a prefix is added.  This allows Imp code to be linked with C
! code while avoid name clashes caused by internal imp procedures with the
! same names as system procedures such as "write" on linux.  To access an
! imp procedure when a system procedure of the same name also has to be accessible,
! create a new declaration for the Imp version, using an alias, such as
! %externalroutine impwrite %alias "_imp_write" (%integer n, p) and call
! impwrite(num, places) instead of write(num, places) because a call to
! unadorned write() will get the system one instead.  (You will of course
! have to write a %spec for that or %include some header file such as "linux.inc"

! specs for system calls such as on linux should mirror the C versions.  Sometimes
! a shim procedure will have to be written if, for example, the system procedure
! takes a C style 0-terminated string as a parameter but the Imp program is
! working with Imp style strings wihich have a preceding length byte.  Normal
! users should never need to reverse the order of parameters to cross-call
! library code.

! OOPS!  Using functions rather than constants had an unexpected side-effect:
! initialisations which previously worked OK now fail at Imp77 compile time,
! for example:  %owninteger sym = NL

!%constlongreal          PI = 3.141592653589793238462  ;! Replace with a realfn to lower namespace pollution...
%permlongrealfnspec     PI   {did you know that Intel architecture actually has a hardware instruction to load the PI constant!}

%constinteger           NL = 10
!%permintegerfnspec      NL

!! const integers and reals are handled internal to pass1 but strings need an external declaration in C
!%conststring(1)         SNL = "
!"
%primstring(1)%fnspec   SNL

!%constname             NIL == 0  ;! not sure if this syntax will work...
%constrecord(*)%name Nil == 0

! to add, maybe:
%permroutinespec DISPOSE(%name v)
%permrecord(*)%mapspec NEW(%name v)
! %externalstring(31)%fnspec DATETIME  {date and time where %string(15)}

! %prim routines are implemented by the compiler with the equivalent of inline code
! %perm routines are implemented by 'static inline' defininitions in perms.h
! Calls to C library code are implemented via '%systemroutinespec' in order
! to allow i2c to force correct C external linkage and order of parameter evaluation.
! Calls to library code written in imp use standard '%externalroutine's.


%primlongrealfnspec     float(%integer n)  ;! *CHANGED* https://history.dcs.ed.ac.uk/archive/os/emas/emas2/compilers/imp80/doc/manual/imp80.html
%primstring(1)%fnspec   tostring(%integer c)

! on a 64-bit system this will need to return a %longinteger
%primintegerfnspec      addr(%name variable)

! any parameter which has to represent an address should also
! be a %longinteger once 64-bit architectures are supported.
! I've been considering adding a %address type which would be
! treated like an integer of the appropriate size - but for
! the imptoc project, not i2c (icode to c), because we are
! stuck with the imp77 parser for i2c which is not readily modifiable.

! (and for compiling pre-64bit code, we probably need to invent
! a "%integers %long" statement!)

{ 8 bits }
%primbyteintegermapspec     byte(%integer n)
%primbyteintegermapspec     byteinteger(%integer n)
{ 16 bits }
%primshortintegermapspec    short(%integer n)
%primshortintegermapspec    shortinteger(%integer n)
!%primhalfintegermapspec    half(%integer n)            {Imp80, maybe 68K}
!%primhalfintegermapspec    halfinteger(%integer n)
{ 32 bits }
%primintegermapspec         integer(%integer n)
%primintegerfnspec          shorteni(%longinteger i)     ;! *ADDED*
{ 64 bits }
%primlongintegermapspec     longinteger(%integer n)
%primlongintegerfnspec      lengtheni(%integer i)   ;! *CHANGED* 
! No long long real: %primlonglongrealfnspec     lengthenr(%longreal r)   ;! *ADDED* 
!!{ 128 bits }
!!%primlonglongintegermapspec longlonginteger(%integer n)   ;! *REMOVED*
{ 32 bits }
%primrealmapspec            real(%integer n)
{ 64 bits }
%primlongrealmapspec        longreal(%integer n)
! No long long real: %primlongrealfnspec         shortenr(%longlongreal r)       ;! *ADDED*
! { 128 bits }
! No long long real: !%primlonglongrealmapspec    longlongreal(%integer n)
{ 255 bytes }
%primstring(*)%mapspec      string(%integer n)
%primrecord(*)%mapspec      record(%integer n)

! These two should be changed to %string (*) %name s if Imp semantics of
! assigning to the length byte are to be supported.  The definitions below
! do not legitimately support changing the length of the string value parameter!
%primbyteintegermapspec     length(%string(*)%name s)
%primbyteintegermapspec     charno(%string(*)%name s,%integer n)


%primintegerfnspec        typeof(%name n)

! This function returns a code which indicates the type of the
! object supplied as parameter.  The complete list of code values
! which may be returned by TYPE OF is as follows:
!
! 0 - unknown type
! 1 - integer
! 2 - real
! 3 - string
! 4 - record
! 5 - byte integer
! 6 - short integer
! 7 - long integer
! 8 - long real
! 9 - array
! 10 - label
!
! Note that an actual runtime call will be necessary in the case of
! %name parameters, but for known variables, the type information can
! be supplied by the compiler directly.  So, a prim/perm hybrid.
!

%primintegerfnspec        sizeof(%name n)

! similar observations as per typeof above.  The %name parameter
! problem however requires that %name parameters are passed as
! a struct containing the desired address in addition to the
! type and size information.  That descriptor has to be able
! to handle strings and records.

! Some of the perm routines can be implemented by passing the buck
! to C library routines.  To future-proof this a little I am specifying
! all calls to the C library as %permroutine rather than %externalroutine.

! Note that the C library supports multiple versions of some of these
! calls depending on the parameter and result type.

%permlongrealfnspec       arcsin(%longreal angle)
%permlongrealfnspec       arccos(%longreal angle)
%permlongrealfnspec       arctan(%longreal x,y)
%permlongrealfnspec       arctan1(%longreal angle)

%permlongrealfnspec       sin(%longreal angle)
%permlongrealfnspec       cos(%longreal angle)
%permlongrealfnspec       tan(%longreal angle)

%permlongrealfnspec       cot(%longreal angle)           ;! *ADDED*

%permlongrealfnspec       fraction(%longreal r)
%permlongrealfnspec       frac pt(%longreal r)

%permintegerfnspec        imod(%integer i)
%permlongrealfnspec       mod(%longreal r)

%permintegerfnspec        int(%longreal r)
%permintegerfnspec        int pt(%longreal r)

! No long long real: %permlongintegerfnspec    lint(%longlongreal r)      ;! *CHANGED*
! No long long real: %permlongintegerfnspec    lint pt(%longlongreal r)   ;! *CHANGED*

%permintegerfnspec        trunc(%longreal r)
%permintegerfnspec        round(%longreal r)

%permlongintegerfnspec    iexp(%integer num, power)
%permlongrealfnspec       rexp(%longreal num, power)
%permlongrealfnspec       exp(%longreal num)         ;! *ADDED*

%permlongrealfnspec       log(%longreal X)
! Normally I would make REM a prim to allow use of hardware instr which does
! both div and remainder together but for imp to C that may be unnecessary
%permintegerfnspec        rem(%integer p,q)
%permlongrealfnspec       sqrt(%longreal num)
%permintegerfnspec        isqrt(%longreal num)
%permintegerfnspec        mul div(%integer A, B, C )
%permlongrealfnspec       radius(%longreal a, b)       ;! *ADDED*
! Perms are roughly equivalent to externals at level -1, however
! they *may* be implemented by macros.  Therefore Perms should not
! be assumed to usable as procedure parameters - if needed, the
! traditional method is to wrap them in another procedure.

%permroutinespec          prompt(%string(15) s) {NOTE: JDM has %string(31) s)}

! read symbol and read ch *should* take a non-specific %name parameter but
! until I implement those properly, %integername is preferable to %byteintegername...
%permroutinespec          read symbol(%integername p)   {NOTE: imp80 and JDM's imp2026 use %name p}
                            { *not* %byteintegername, which may cause problems }
%permroutinespec          read ch(%integername p)  {imp80: %name}

%permintegerfnspec        next symbol  { 'symbol' does cr/lf filtering if appropriate }
%permintegerfnspec        next ch      { whereas 'ch's are raw and may return CR }
%permroutinespec          skip symbol
%permroutinespec          print symbol(%byteinteger sym) {NOTE: JDM's imp2026 uses %integer}
%permroutinespec          print ch(%byteinteger sym)
%permroutinespec          print string(%string(255)  s)
%permroutinespec          write(%integer  value, places)

! "item"s are just 1-character strings.
%permroutinespec          read item(%string(*)%name s)
%permroutinespec          read string(%string(*)%name s)
%permroutinespec          read text(%string(*)%name s,%integer delim)
%permstring(1)%fnspec     next item
%permroutinespec          read line(%string(*)%name s)

! some redundancy in some of the procedure names to accommodate the many various
! implementations of IMP throughout the years.

! set up in1,in2,in3/out1,out2,out3 equivalent from argv:
%permroutinespec          set streams       { this is from the Dec10! }

%permintegerfnspec        instream
%permintegerfnspec        outstream
%permintegerfnspec        input stream
%permintegerfnspec        output stream
%permintegerfnspec        outdev
%permstring(255)%fnspec   input name
%permstring(255)%fnspec   output name
%permstring(255)%fnspec   in file name
%permstring(255)%fnspec   out file name
%permroutinespec          select input(%integer  n)
%permroutinespec          select output(%integer  n)
%permroutinespec          open input(%integer  n, %string(255) fd)
%permroutinespec          open output(%integer  n,%string(255) fd)
%permroutinespec          open binary input(%integer  n, %string(255) fd)
%permroutinespec          open binary output(%integer  n,%string(255) fd)
%permroutinespec          define input(%integer i,%string(255) spec)
%permroutinespec          define output(%integer i,%string(255) spec)
%permroutinespec          abandon input
%permroutinespec          abandon output
%permroutinespec          close stream(%integer stream)
%permroutinespec          close input
%permroutinespec          close output
%permroutinespec          reset input
%permroutinespec          reset output
%permroutinespec          complete input
%permroutinespec          complete output
%permroutinespec          position input(%integer p)
%permroutinespec          position output(%integer p)
%permroutinespec          input position
%permroutinespec          output position
%permpredicatespec        end of input
%permintegerfnspec        outpos
%permintegerfnspec        inpos
%permroutinespec          space
%permroutinespec          spaces(%integer  n)
%permroutinespec          newpage
%permroutinespec          newline
%permroutinespec          newlines(%integer n)
%permroutinespec          read(%name ptr)
%permroutinespec          print(%longreal r, %integer before, %integer after)
%permroutinespec          print floating(%longreal r, %integer a, b)
%permroutinespec          print fl(%longreal r, %integer places)
%permroutinespec          print f hex(%longreal r)  ;! mainly for compiler debugging
%permroutinespec	  to upper(%string(*)%name s)
%permroutinespec	  to lower(%string(*)%name s)
%permstring(255)%fnspec   sub string(%string(*)%name s, %integer from, to) { Changed from %string(255) s }
%permstring(255)%fnspec   from string(%string(*)%name s, %integer from, to) { Ditto }
%permstring(255)%fnspec   trim(%string(255) s, %integer max)
%permstring(8)%fnspec     time
%permstring(9)%fnspec     date
%permintegerfnspec        cpu time { Imp77-style version returning ms in an integer. }
!%permrealfnspec          cpu time { EMAS-style version, returning seconds as a %real! }
%permstring(255)%fnspec   cli param
%permintegerfnspec        freestore {mainly just imp15}
%permstring(255)%fnspec   itos(%integer i,pos)
%permintegerfnspec        stoi(%string(255) s)

! Note that later Imp77 used 'sub' rather than 'subevent'.  Once %or is implemented
! properly for records, I'll overlay sub and subevent for portability, but until then
! I think I need to be consistent and use 'subevent' as far more old code uses that
! than uses 'sub'. (In fact I haven't found any code yet that uses 'sub', although
! I see the Dec10 perms uses sub in the event fm definition...) 
%record %format event fm(%integer event,
                         %integer sub{event},
                         %integer info,
                         %string(255){%name} message,
                         %integer line,
                         %string(255)%name file)

! Note the ambiguity between the event field when exported as an integer
! and the event (eventfm) record containing these three fields (and more)
! as per more recent versions of Imp77.  Not quite sure yet how translated
! imp programs are going to handle that - a command-line option may be needed

! Meanwhile, to access the standard 'event' fn, redeclare it locally within the
! scope which uses it, as %externalintegerfnspec event %alias "_imp_eventno"

! by default, 'event' is:
%permrecord (event fm) %fn %spec event   {Note: %fn.  JDM uses %map}

%permintegerfnspec        eventno
%permintegerfnspec        subevent
%permintegerfnspec        event inf    ;! *ADDED*
%permintegerfnspec        event info
%permstring(255)%fnspec   event message
%permintegerfnspec        event line
%permstring(255)%fnspec   event file

! Linux-specific argument handling:
%permintegerfnspec argc
%permstring(255)%fnspec argv(%integer idx)

! Signal event codes

! To reduce namespace pollution these would be better as %permconstinteger - if such a thing existed.
! Unfortunately plain %constinteger means these cannot be redeclared by the user
! Most alternative formulations will stop them being used in any context where a constant is
! required by Imp... (such as the first parameter to %signal ...)

! What *is* possible - although rather dirty - is to specify these as a permintegerfn
! and do the actual definition in the C header files using macros or a static inline fn.

%constinteger PROGRAM STOP = 0
%constinteger OVERFLOW = 1
%constinteger STORE EXCEEDED = 2
%constinteger CONVERSION ERROR = 4
%constinteger ARGUMENT ERROR = 5
%constinteger RANGE ERROR = 6
%constinteger STRING RESOLUTION FAILS = 7
%constinteger UNDEFINED VALUE = 8
%constinteger INPUT ENDED = 9
%constinteger IO ERROR = 10
%constinteger JUMP OUT = 11
%constinteger USER EVENT = 12

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

! Imp80:
!%permarraymapspec array(%integer a1addr,%arrayname format)
!%permroutinespec pprofile

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

! Temporarily added for compatibility with JDM's imp2026 perms.
! If any of these are actually wanted they might be better implemented
! in an explicit header file such as '%include "unix.inc"'...

%constinteger File Sep = '='
%constinteger cMaxSigFigs = 15

{ Routine to map result of "typeof" function to a string }
{ Starting point for Seek Input, Seek Output random access routines }
%permstring(255)   %fn %spec   typeName( %integer t )

%constinteger from start   = 0   {SEEK_START etc from C}
%constinteger from current = 1
%constinteger from end     = 2

%permroutine           %spec   Seek Input( %integer displacement, %integer start point)
%perminteger       %fn %spec   Tell Input
%perminteger       %fn %spec   Read Buffer( %name ptr, %integer count )
%permstring(31)    %fn %spec   Current Prompt
%perminteger       %fn %spec   Get Max Input Stream
%permpredicate         %spec   File End

{ Special input routines (only intended for use by pass2 of the compiler) }
{ These allow the "end of input" event to be switched on/off              }
%permroutine           %spec   allow eof event
%permroutine           %spec   block eof event

{ Output routines }
%perminteger       %fn %spec   Old Output Stream
%permroutine           %spec   Seek Output( %integer displacement, %integer start point)
%perminteger       %fn %spec   Tell Output
%perminteger       %fn %spec   Write Buffer( %name ptr, %integer count )
%perminteger       %fn %spec   Get Max Output Stream
%permroutine           %spec   Flush Output

%permroutine           %spec   print real( %longreal f, %integer sf, places )
%permroutine           %spec   print float( %longreal f, %integer sf )
%permstring(255)   %fn %spec   F to S( %longreal f, %integer sf )

{ multi-number base version of I to S (number base in range 2..36) }
%permstring(255)   %fn %spec	int2ascii( %integer nn, base, places )

{ Enhanced version of int2ascii which indicates number base before the number }
%permstring(255)   %fn %spec	formatNumber( %integer n,base,places)

{ Convert number to a hex/bin string (no sign prefix) }
%permstring(255)   %fn %spec	int2hex( %integer nn, places )
%permstring(32)    %fn %spec	int2bin( %integer nn )
%permstring(16)    %fn %spec	short2bin( %shortinteger nn )

{ These next 5 functions only work on those %name parameters which reference }
{ an array's (Dope Vector Pointer,Array Data Address)                        }
%perminteger       %fn %spec   array entry size( %name n )
%perminteger       %fn %spec   array entry type( %name n )
%perminteger       %fn %spec   array dimensions( %name n )
%perminteger       %fn %spec   array lower bound( %name n, %integer index )
%perminteger       %fn %spec   array upper bound( %name n, %integer index )

{ Experimental library routines in IMP (calling external C routines) }

{ Routines to access the environment variables (implemented in IMP) }
%perminteger       %fn %spec   get env count                      
%permstring(255)   %fn %spec   get env name( %integer index )
%permstring(255)   %fn %spec   get env( %integer index )          
%perminteger       %fn %spec   get env index( %string(255) varname )
%permstring(255)   %fn %spec   get env as string( %string(255) envname )
%perminteger       %fn %spec   get env as integer( %string(255) varname )

{ Routines to access the command line parameters (implemented in IMP) }
{ NB getarg(0) == prog name                                           }
{    getarg(i) == i'th parameter                                      }
{    last command line parameter has index = getargcout - 1           }
%perminteger       %fn %spec   getargcount                         {argc}
%permstring(255)   %fn %spec   getarg( %integer index )            {argv(i)}

{ Example routines to provide limited mathematical functions/values }
%permlongreal      %fn %spec   sine( %longreal x )   {== sin(x)}
%permlongreal      %fn %spec   cosine( %longreal x ) {== cos(x)}
%permlongreal      %fn %spec   Log2(%longreal x )
%permlongreal      %fn %spec   Log10(%longreal x )

{ Additional C utility routines to exit program }
{ I guess needed because %stop doesn't allow setting rc=1 ?}
%permroutine           %spec   exit( %integer status )

%control 0

! resume outputing generated C code:
%list
%endofperm
