Category:Polyglot:PL/I and PL/M: Difference between revisions

From Rosetta Code
Content added Content deleted
(→‎Implementation: more notes and include files.)
Line 44: Line 44:
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.
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.
<br><br>
<br><br>
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.
<br>
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 <code>%include</code> statement but the original 8080 PL/M compiler does not support file inclusion so the relevent definitions must be included in each program.
<br><br>
A suitable file for PL/I definitions could be:
<br>
<lang pli>/* pg.inc: PL/I definitions for "polyglot PL/I and PL/M programs" compiled with PL/I */

declare eof binary( 15 )fixed; /* used to allow the PL/M "EOF" keyword */
/* to appear at the end of the program */
/* in an assignment "EOF = 1;" */
/* the PL/M compiler will ignore any */
/* text after the "EOF" */

%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;

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


/* end pg.inc */</lang>

For PL/M, the following definitions would be used, with the appropiate subset cut-and-pasted into the programL
<lang pli> DECLARE BINARY LITERALLY 'ADDRESS', CHARACTER LITERALLY 'BYTE';
DECLARE SADDR LITERALLY '.', BIT LITERALLY 'BYTE';
DECLARE TRUE LITERALLY '1', FALSE LITERALLY '0';
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;</lang>

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.
See below for some examples.

Revision as of 18:13, 17 December 2021

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. However some features of the languages can be exploited to make sources that are valid in both languages - although 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.



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.

Implementation

The following stratagy could be used:

  • The program will start with n100H: procedure options (main); where the "(main)" starts in column 81.
  • after the procedure header, a variable called eof will be declared with declare eof fixed binary;.
  • The procedure header and the declaration of eof 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"
  • an assignment to EOF will appear immediately before the final "end" of the program - here, EOF will be specified 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:
<lang pli>/* pg.inc: PL/I definitions for "polyglot PL/I and PL/M programs" compiled with PL/I */

  declare eof binary( 15 )fixed; /* used to allow the PL/M "EOF" keyword */
                                 /* to appear at the end of the program  */
                                 /* in an assignment "EOF = 1;"          */
                                 /* the PL/M compiler will ignore any    */
                                 /* text after the "EOF"                 */
  %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;
  toupper: procedure( c )returns( character( 1 ) );
     declare c character( 1 );
     return ( translate( c, upper_case, lower_case ) );
  end toupper;


/* end pg.inc */</lang>

For PL/M, the following definitions would be used, with the appropiate subset cut-and-pasted into the programL <lang pli> DECLARE BINARY LITERALLY 'ADDRESS', CHARACTER LITERALLY 'BYTE';

  DECLARE SADDR  LITERALLY '.',       BIT       LITERALLY 'BYTE';
  DECLARE TRUE   LITERALLY '1',       FALSE     LITERALLY '0';
  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;</lang>

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.