Category:Polyglot:PL/I and PL/M

From Rosetta Code
(Redirected from Polyglot:PL/I and PL/M)
Language
Polyglot:PL/I and PL/M
This programming language may be used to instruct a computer to perform a task.
See Also:


Listed below are all of the tasks on Rosetta Code which have been solved using Polyglot:PL/I and PL/M.

A Polyglot program is a program whose source is a valid program in two or more languages, producing the same results when run in the different languages.

PL/I and PL/M

Although similar, PL/I and PL/M are not the same language. A relatively simple pre-compiler could probably handle the differences for simple programs, but why write a pre-compiler when fetures of the languages/compilers can be exploited to make sources that are valid in both PL/I and PL/M ( even if some stylisation is required ) ?

In this, the PL/M language as implemented by Gary Kildall's original 8080 PL/M Compiler will be considered.

8080 PL/M features of interest:

  • The compiler ignores everything after column 80 of a source line.
  • The compiler treats lower case letters and many "special" characters as spaces.
  • PL/M has a parameterless macro facility.
  • The PL/M source must end with an EOF keyword - everything after it is ignored.
  • The source must start with 100H: which sets the origin for CP/M programxs.
  • A PL/M program is a sequence of statements and declarations, not a main procedure.
  • PL/M has no builtin I/O statements - under CP/M it is possible to call OS routines.
  • MOD is an operator, AND, OR and NOT are keywords.
  • The only types are BYTE and ADDRESS - unsigned 8 and 16 bit integers.
  • Identifiers cannot contain underscores - the PL/M compiler treats them as spaces - dollar signs can appear but are ignored.
  • Keywords are reserved in PL/M.



PL/I features of interest:

  • PL/I is not (usually) case sensitive.
  • PL/I has a powerful in-built pre-proessor, however this is not implemented in all PL/I compilers.
  • A PL/I program consists of a main procedure which contains all declarations and statements.
  • The main procedure is declared as having "options(main)".
  • I/O statements are built in to the language.
  • MOD is a function, &, | and ^ (or ¬) are used for AND, OR and NOT.
  • PL/I has a range of types, none of which are called BYTE or ADDRESS.
  • Identifiers can contain underscores and some implementations allow dollar signs.
  • Keywords are not reserved in PL/I.

Arrays

In PL/I when an array is declared, the lower boiund can be omitted and defaults to 1. The upper bound is the dimension specified in the declaration.
E.g. declare a ( 100 ) fixed binary; declares an array of 100 integers, the subscripts range from 1 to 100.

In PL/M when an array is declared, the lower bound cannot be specified and is always 0. The upper bound is one less than the dimension specified in the declaration.
E.g. DECLARE A( 101 ) ADDRESS; declares an array of 101 integers, the subscripts range form 0 to 100.

PL/M only allows arrays with a single subscript to be declared. PL/I allows multi-dimensional arrays.

Implementation

The following stratagy could be used:

  • The program will start with n100H: procedure options (main); where the "(main)" starts in column 81.
  • The procedure header will be in lower case except for the final "H" of "100H" - there will be no digits in the procedure name, other than the final "100"
  • The final "end" of the program will be labelled EOF, in upper-case.
  • Code that is specific to PL/M will be commented out by having the opening "/*" of the comment appear in column 81.
  • The code that is specific to PL/M will have "/* */" at the end - the "/* */" will terminate before column 81.
  • Code that is specific to PL/I will generally be commented out by placing "/* */" in column 78, so that the "*/" is invisible to the PL/M compiler.
  • Additionally, some PL/I code can be commented out by using a macro to add a "/*" to a PL/I keyword and following the code with "/* */".



Note that for some PL/I compilers, it may be necessary to specify a compiler option to set the margins for the code, so the source line can be up to say, 120 characters wide.

A "lowest common denominator" approach will be used with a common set of procedures providing the I/O and a limited range of types used.
As noted above PL/M only has 8 and 16 bit unsigned integers. The PL/M BYTE type is 8 bits and can be used where a character( 1 ) or bit( 1 ) item would be used in PL/I.

Include Files

PL/I allows file inclusion via the %include statement but the original 8080 PL/M compiler does not support file inclusion so the relevent definitions must be included in each program.

A suitable file for PL/I definitions could be:

/* pg.inc: PL/I definitions for "polyglot PL/I and PL/M programs" compiled with PL/I */

   %replace true by '1'b, false by '0'b;

   declare lower_case character( 26 ) static initial( 'abcdefghijklmnopqrstuvwxyz' );
   declare upper_case character( 26 ) static initial( 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' );

   /* print a character */
   prchar: procedure( c );
      declare c character( 1 );
      put edit( c )( a( 1 ) );
   end prchar;

   /* print a newline */
   prnl:   procedure;
     put skip;
   end prnl;

   /* print a number in the minimum field width */
   prnumber: procedure( n );
      declare n binary( 15 )fixed;
      if      n <    10 then put edit( n )( f( 1 ) );
      else if n <   100 then put edit( n )( f( 2 ) );
      else if n <  1000 then put edit( n )( f( 3 ) );
      else if n < 10000 then put edit( n )( f( 4 ) );
      else                   put edit( n )( f( 5 ) );
   end prnumber;

   /* print a "$" terminated string */
   prstring: procedure( s );
      declare s character( 80 )varying;
      declare ( p, len ) binary( 15 )fixed;
      declare c character( 1 );
      len = length( s );
      if len > 1 then do;
         p = 1;
         c = substr( s, p, 1 );
         do while( p <= length( s ) & c ^= '$' );
            call prchar( c );
            p = p + 1;
            if p <= len then c = substr( s, p, 1 );
         end;
      end;
   end prstring;

   /* read a character from the keyboard, with a carriage-return following it */
   rdchar: procedure( dummy )returns( character( 1 ) );
      declare dummy binary( 15 )fixed;
      declare c character( 1 );
      get edit( c )( a( 1 ) );
      get skip;
      return ( c );
   end rdchar;

   /* allows PL/M code to say "CALL PRSTRING( SADDR( 'ABC' ) );" */
   /*                      where SADDR is declared LITERALLY '.' */
   saddr:   procedure( s )returns( character( 80 )varying );
      declare s character( 80 )varying;
      return ( s );
   end saddr;

   /* returns a MOD b */
   modf:    procedure( a, b )returns( binary( 15 )fixed );
      declare ( a, b ) binary( 15 )fixed;
      return ( mod( a, b ) );
   end modf;

   /* returns not p */
   not:     procedure( p )returns( bit( 1 ) );
      declare p bit( 1 );
      return( ^ p );
   end not;

   toupper: procedure( c )returns( character( 1 ) );
      declare c character( 1 );
      return ( translate( c, upper_case, lower_case ) );
   end toupper;


/* end pg.inc */

For PL/M, the following definitions would be used, with the appropiate subset cut-and-pasted into the program:

   DECLARE BINARY LITERALLY 'ADDRESS', CHARACTER LITERALLY 'BYTE';
   DECLARE FIXED  LITERALLY ' ',       BIT       LITERALLY 'BYTE';
   DECLARE STATIC LITERALLY ' ',       RETURNS   LITERALLY ' ';
   DECLARE FALSE  LITERALLY '0',       TRUE LITERALLY '1';
   DECLARE HBOUND LITERALLY 'LAST',    SADDR  LITERALLY '.';
   BDOSF: PROCEDURE( FN, ARG )BYTE;
                               DECLARE FN BYTE, ARG ADDRESS; GOTO 5;   END; 
   BDOS: PROCEDURE( FN, ARG ); DECLARE FN BYTE, ARG ADDRESS; GOTO 5;   END;
   PRSTRING: PROCEDURE( S );   DECLARE S ADDRESS;   CALL BDOS( 9, S ); END;
   PRCHAR:   PROCEDURE( C );   DECLARE C CHARACTER; CALL BDOS( 2, C ); END;
   PRNL:     PROCEDURE;        CALL PRCHAR( 0DH ); CALL PRCHAR( 0AH ); END;
   PRNUMBER: PROCEDURE( N );
      DECLARE N ADDRESS;
      DECLARE V ADDRESS, N$STR( 6 ) BYTE, W BYTE;
      N$STR( W := LAST( N$STR ) ) = '$';
      N$STR( W := W - 1 ) = '0' + ( ( V := N ) MOD 10 );
      DO WHILE( ( V := V / 10 ) > 0 );
         N$STR( W := W - 1 ) = '0' + ( V MOD 10 );
      END; 
      CALL BDOS( 9, .N$STR( W ) );
   END PRNUMBER;
   RDCHAR: PROCEDURE( DUMMY )BYTE;
      DECLARE DUMMY ADDRESS;
      DECLARE C BYTE;
      DECLARE X BYTE;
      C = BDOSF( 1, 0 );
      DO WHILE( C = 0DH OR C = 0AH );
         CALL PRNL; C = BDOSF( 1, 0 );
      END;
      X = C;
      DO WHILE( X <> 0DH AND X <> 0AH );
         X = BDOSF( 1, 0 );
      END;      
      CALL PRNL;
      RETURN ( C );
   END RDCHAR ;
   TOUPPER:  PROCEDURE( C )BYTE;
      DECLARE C BYTE;
      IF   C >= 97 AND C <= 122
      THEN RETURN ( ( C - 97 ) + 'A' );
      ELSE RETURN ( C );
   END TOUPPER;
   MODF: PROCEDURE( A, B )ADDRESS;
      DECLARE ( A, B )ADDRESS;
      RETURN( A MOD B );
   END MODF;
   MIN: PROCEDURE( A, B ) ADDRESS; 
      DECLARE  ( A, B ) ADDRESS;
      IF A < B THEN RETURN( A ); ELSE RETURN( B );
   END MIN;
   MAX: PROCEDURE( A, B ) ADDRESS; 
      DECLARE  ( A, B ) ADDRESS;
      IF A > B THEN RETURN( A ); ELSE RETURN( B );
   END MAX;

Note the lack of comments in the PL/M "include" file - this is because the definitions will be commented out for PL/I compilers by having a "/*" starting in column 81 preceeding the definitions and /* */ follow them.

See below for some examples.