Wednesday, 25 October 2017

RetroChallenge 2017/10: Part 6

Writting New Software for an NEC PC-8401


As noted previously, the PC-8401 came sans BASIC interpreter, which was quite an anathema in the day. This lack of BASIC presents no real impediment in producing new software for the machine today, even back in the 80s a CPM targeted variety of BASIC could be sourced for the machine (if not a specific PC-8401 version). 30 years on, being equipt with a Z80A processor and the CPM 2.2 operating systems puts this laptop a good position for some software development.


Getting into BASIC


To start we'll need to copy BASIC interpreters onto the PC-8401. AS with all software, BASIC will be taking up valuable chunks of the 32k reserved as drive space. Our own BASIC text files will then need to fit in whatever space remains. Ideally we'd have a RAM extension, either a PC-8406A 32K RAM pack or a PC-8407A 128K pack, in order to program something of a reasonable size. Still even without a RAM pack we can at least try out a number of BASIC flavors.

Both the versions of BASIC mentioned below are available from Gary J. Webes' excellent NEC PC-8201 support site WEB8201, where he holds a selection of software for the NEC PC-8401. NEC PC-8401. Check out the NEC 8401A / 8500 File Collection portion of the File Downloads page.

Microsoft BASIC


In the stakes to use the greatest amount of memory possible, Microsoft BASIC wins hands down. The last CPM version 5.29 will take up huge 23.4k of 'disk space' and when executed leaves a little over 1k of active memory on the PC-8401 to play around with. On the plus side, it's the version of BASIC that's the most familiar, and baring some commands unavailable in the CPM version, there are a wealth of examples laying around to learn from.

Unfortunately the size of MS BASIC makes it a non stater, it's more of a curiosity than something of practical usage. There are compilers available for MS BASIC, though a you still require libraries on the disk to run complied applications. Due to lack of disk space, compilation would need to be run on a host computer before moving the end product over to the PC-8401.


ZBAS


Luckily there are other options, ZBAS is quite a usable option only requiring 16.4k, leaving us with some room to write programs interactively, and some space to save these applications to disk.

Interestingly and extra fortunately when saving files, ZBAS tokenises BASIC Commands, much like on a 128k ZX Spectrum. Thus allowing for some larger than expected applications to be saved on our rather limited 'solid' state storage.

If your going to leave BASIC laying around on the System, then ZBAS is clearly the one to have.


Moving to Modern 'C' Developement


While there are plenty of period development platforms and languages available for CPM, including 'C's and Pascals, you'll need to run these in a emulated environment as there is no room on the PC-8401 for such niceties. A load of CPM software and utilities are available on the retroarchive, although none of these programs are specific to the PC-8401.



Z88DK


Perhaps the easiest way to pursue software development for a CPM machine and the PC-8401 today is via the use of Z88dk. Z88dk is a 'C' compiler targeting TRS80s, Commodore 128s, Sinclair ZX81s and just about any machine containing a Z80 processor. Specifically for out purposes there is a dedicated CPM library.

In order to test Z88DK out with the PC-8401 I borrowed from an older ZX81 program I wrote up some time ago, "ZX Roman Numeral-Izer". The program is a simple roman numeral format checker, it takes Roman numerals as an argument and then correctly formats them to the agreed standards (Standards that even the Romans didn't always adhere to). After a couple of minor changes to remove some specific ZX81 extensions and put in some specific PC-8401-isations the project compiled perfectly.




If you're interested then the full source is below, bit of a rush and there will be better ways to achieve the same result I'm sure, still proof of concept proven.


NEC PC-8401 Roman Numeral-iser Listing

// To Compile for CPM do as below.
// zcc +cpm -lm -o rome.com rome.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef enum { true = 0, false = !true } bool;

static short isaNumArab[7] = {1, 5, 10,50,100,500,1000};
static char csaNumRome[8] = "ivxlcdm";

// Function Get Arabic value of Roman Numeral
short GetArabValue(char csMyNumerals[15]){

 char csMyNumeral[2];
 char csNumRome[2];

 short iNumberNow = 0;
 short iNumberLast = 0;
 short iNumberTotal = 0;

 short iCount1 = 0;
 short iCount2 = 0;

 bool bTest = false;
 bool bNegative = false;

 //Go through sMyNumerals input string backwards for easier rule matching
 for (iCount1=strlen (csMyNumerals)-1; iCount1>=0; iCount1--){
  strncpy(csMyNumeral, csMyNumerals+(iCount1*1), 1);
  csMyNumeral[1]='\0';

  for (iCount2=0; iCount2<=strlen(csaNumRome)-1; iCount2++){
   strncpy(csNumRome, csaNumRome+(iCount2*1), 1);
   csNumRome[1]='\0';
   bTest = strnicmp(csMyNumeral, csNumRome,1);

   if (bTest == true){
    iNumberNow = isaNumArab[iCount2];

    if ((iNumberNow > iNumberLast) || ((iNumberNow == iNumberLast) && (bNegative == false))){
     iNumberTotal = iNumberTotal + iNumberNow;
     bNegative = false;
    } else {
     iNumberTotal = iNumberTotal - iNumberNow;
     bNegative = true;
    }
   }
   iNumberLast = iNumberNow;
  }
 }

 return iNumberTotal;
}

// Function Get Roman value of Arabic Numeral
void PrintRomeNumerals(int iMyNumberArab){
 char csNumRome[16];
 char csMyNumerals[16];
 char csMyNumeral[2];

 short iTemp[16];

 short iNumberNow;

 short iCount1 = 0;
 short iCount2 = 0;
 short iCount3 = 15;

 memset(csNumRome,0,strlen(csNumRome));

 memset(iTemp, 0, sizeof(iTemp));

 if(iMyNumberArab>=4000){
  strcpy(csNumRome,"Max Value 3999");
 } else {

  itoa(iMyNumberArab,csMyNumerals,10);
  //csMyNumerals[15]='\0';
  //Go through sMyNumerals input string backwards for easier rule matching

  for (iCount1=strlen(csMyNumerals)-1; iCount1>=0; iCount1--){

   strncpy(csMyNumeral, csMyNumerals+(iCount1*1), 1);
   iNumberNow = atoi(csMyNumeral);

   switch (iNumberNow) {
    case 1: case 2: case 3:
     for (iCount2=0;iCount2<iNumberNow;iCount2++){

      iTemp[iCount3] = csaNumRome[(strlen(csMyNumerals)-1)*2-iCount1*2];

      iCount3--;
     }

    break;

    case 4:

      iTemp[iCount3] = csaNumRome[(strlen(csMyNumerals)-1)*2-iCount1*2+1];
      iTemp[iCount3-1] = csaNumRome[(strlen(csMyNumerals)-1)*2-iCount1*2];
      iCount3=iCount3-2;
    break;

    case 5: case 6: case 7: case 8:

     for (iCount2=0;iCount2<iNumberNow-5;iCount2++){

      iTemp[iCount3] = csaNumRome[(strlen(csMyNumerals)-1)*2-iCount1*2];
      iCount3--;
     }
     iTemp[iCount3] = csaNumRome[(strlen(csMyNumerals)-1)*2-iCount1*2+1];
     iCount3--;
    break;

    case 9:

     iTemp[iCount3] = csaNumRome[(strlen(csMyNumerals)-1)*2-iCount1*2+2];
     iTemp[iCount3-1] = csaNumRome[(strlen(csMyNumerals)-1)*2-iCount1*2];
     iCount3=iCount3-2;
    break;
   }

  }
 }

 printf("\n Correct Format: ");
 for (iCount2=0;iCount2<16;iCount2++){
  if(iTemp[iCount2]!=0){
   printf("%c",iTemp[iCount2]);
  }

 }
 printf("\n");

 return;
}


void BannerLine(short iLength, char cCharter){
 short iCount1 = 0;
 for (iCount1=0; iCount1<iLength; iCount1++){
   printf("%c",cCharter);
 }
}


void BannerSet(void){
 short iCount1 = 0;
 printf("\n");
 BannerLine(32,149);
 printf( "\n PC-8401 Roman Numeral-iser\n" );
 BannerLine(32,149);

 for(iCount1=0;iCount1<=strlen(csaNumRome)-1;iCount1++){

  printf("\n Numeral: %c = Numeric: %d",csaNumRome[iCount1],isaNumArab[iCount1]);
  if(iCount1 == strlen(csaNumRome)-1){
   printf("\n");
  }
 }

 BannerLine(32,149);

 printf("\n Roman Numeral Format Check\n");

 BannerLine(32,149);

}


short PressAnyKey(void){

 char chr;

 BannerLine(32,149);
 printf("\n Input Another Numeral? Y/N: ");
 chr = getchar();

 if(chr == 'n' || chr == 'N'){
  return 1;
 } else {
  return 0;
 }
}


main(void)
{

 char csMyNumerals[15];

 bool PressKey = true;

 char csMyNumberRome[15];
 int iMyNumberArab = 0;

 while( PressKey != false ) {

  BannerSet();

  printf("\n Roman Numerals: ");
  fgets(csMyNumerals,15,stdin);
  iMyNumberArab = GetArabValue(csMyNumerals);
  printf(" Numeric  Value: %d",iMyNumberArab);

  PrintRomeNumerals(iMyNumberArab);

  memset(csMyNumerals,0,strlen(csMyNumerals));
  memset(csMyNumberRome,0,strlen(csMyNumberRome));

  PressKey = PressAnyKey();

 }
}

See RetroChallenge IntroPart 1Part_2Part 3Part 4, Part 5Part 6Part 7Part 8