100H: /* 8080 MACHINE CODE TO ADD TWO BYTES: 79 MOV A,C ; LOAD FIRST ARG INTO ACCUMULATOR 83 ADD E ; ADD SECOND ARG TO ACCUMULATOR C9 RET ; RETURN */ DECLARE ADD$8080 DATA (79H, 83H, 0C9H); /* THE 8080 PL/M CALLING CONVENTION IS THAT THE NEXT-TO-LAST ARG IS PUT IN (B)C, THE LAST ARG IN (D)E. (THE REST ARE IN MEMORY BUT WE DO NOT NEED ANY MORE.) THE RETURN ARGUMENT SHOULD BE IN THE ACCUMULATOR. WE CAN DEFINE A WRAPPER PROCEDURE TO DECLARE THE TYPES OF THE ARGUMENTS. */ EXEC$ADD: PROCEDURE (A,B) BYTE; DECLARE (A,B) BYTE; /* WE CAN 'GO TO' CONSTANTS OR VARIABLES, BUT NOT TO EXPRESSIONS. SO WE HAVE TO FETCH THE ADDRESS FIRST. */ DECLARE LOC ADDRESS; LOC = .ADD$8080; GO TO LOC; END EXEC$ADD; /* IN FACT, PL/M DOES NOT COME WITH ANY STANDARD LIBARIES. IT IS FROM BEFORE THE TIME THAT YOU COULD ASSUME THERE WOULD EVEN BE AN OPERATING SYSTEM, THOUGH CP/M (THE PREDECESSOR TO DOS) WOULD QUICKLY BECOME STANDARD. WE NEED TO USE THIS EXACT TRICK TO GET CP/M TO PRINT THE RESULT TO THE OUTPUT. LUCKILY (AND NOT COINCIDENTALLY), THE CP/M SYSCALL ENTRY POINT IS COMPATIBLE WITH THE PL/M CALLING CONVENTION. */ BDOS: PROCEDURE (FUNC, ARG); DECLARE FUNC BYTE; DECLARE ARG ADDRESS; /* 5 IS THE CP/M BDOS ENTRY POINT */ GO TO 5; END BDOS; /* WE ALSO NEED OUR OWN NUMBER OUTPUT ROUTINE. WE CAN WRITE IT IN PL/M, THEN USE THE ABOVE ROUTINE TO TELL CP/M TO PRINT THE RESULT. */ PRINT$NUMBER: PROCEDURE(N); DECLARE S (4) BYTE INITIAL ('...$'); DECLARE P ADDRESS; DECLARE (N, C BASED P) BYTE; /* EXTRACT EACH DIGIT AND WRITE THEM BACKWARDS TO A STRING */ P = .S(3); DIGIT: P = P-1; C = (N MOD 10) + '0'; N = N/10; IF N > 0 THEN GO TO DIGIT; /* TELL CP/M TO PRINT THE RESULTING STRING */ CALL BDOS(9, P); END PRINT$NUMBER; /* USING OUR OWN MACHINE CODE WORKS IN THE SAME WAY */ CALL PRINT$NUMBER( EXEC$ADD( 7, 12) ); /* THIS PRINTS 19 */ CALL BDOS(0,0); /* EXIT */ EOF