zmakebas-1.2/0040711000175000001440000000000010100200714011656 5ustar rususerszmakebas-1.2/zmakebas.c0100600000175000001440000006313110044743775013652 0ustar rususers/* zmakebas - convert a text file containing a speccy Basic program * into an actual program file loadable on a speccy. * * Public domain by Russell Marks, 1998. */ /* warning: this is probably the least structured program ever. * I guess that's what comes from hacking something into existence... :-/ */ #include #include #include #ifdef HAVE_GETOPT #include #endif #include #include #if defined(__TURBOC__) && !defined(MSDOS) #define MSDOS #endif #define DEFAULT_OUTPUT "out.tap" #define REM_TOKEN_NUM 234 #define BIN_TOKEN_NUM 196 /* tokens are stored (and looked for) in reverse speccy-char-set order, * to avoid def fn/fn and go to/to screwups. There are two entries for * each token - this is to allow for things like "go to" which can * actually be entered (thank ghod) as "goto". The one extension to * this I've made is that randomize can be entered with -ize or -ise. */ char *tokens[]= { "copy", "", "return", "", "clear", "", "draw", "", "cls", "", "if", "", "randomize", "randomise", "save", "", "run", "", "plot", "", "print", "", "poke", "", "next", "", "pause", "", "let", "", "list", "", "load", "", "input", "", "go sub", "gosub", "go to", "goto", "for", "", "rem", "", "dim", "", "continue", "", "border", "", "new", "", "restore", "", "data", "", "read", "", "stop", "", "llist", "", "lprint", "", "out", "", "over", "", "inverse", "", "bright", "", "flash", "", "paper", "", "ink", "", "circle", "", "beep", "", "verify", "", "merge", "", "close #", "close#", "open #", "open#", "erase", "", "move", "", "format", "", "cat", "", "def fn", "deffn", "step", "", "to", "", "then", "", "line", "", "<>", "", ">=", "", "<=", "", "and", "", "or", "", "bin", "", "not", "", "chr$", "", "str$", "", "usr", "", "in", "", "peek", "", "abs", "", "sgn", "", "sqr", "", "int", "", "exp", "", "ln", "", "atn", "", "acs", "", "asn", "", "tan", "", "cos", "", "sin", "", "len", "", "val", "", "code", "", "val$", "", "tab", "", "at", "", "attr", "", "screen$", "", "point", "", "fn", "", "pi", "", "inkey$", "", "rnd", "", "play", "", "spectrum", "", NULL }; /* the whole raw basic file is written to filebuf; no output is generated * until the whole program has been converted. This is to allow a TAP * to be output on a non-seekable file (stdout). */ #ifdef MSDOS #define MAX_LABELS 500 unsigned char filebuf[32768]; char infile[256],outfile[256]; #else #define MAX_LABELS 2000 unsigned char filebuf[49152]; char infile[1024],outfile[1024]; #endif #define MAX_LABEL_LEN 16 /* this is needed for tap files too: */ unsigned char headerbuf[17]; int output_tap=1,use_labels=0; unsigned int startline=0x8000; int autostart=10,autoincr=2; char speccy_filename[11]; int labelend=0; unsigned char labels[MAX_LABELS][MAX_LABEL_LEN+1]; int label_lines[MAX_LABELS]; unsigned char startlabel[MAX_LABEL_LEN+1]; #ifndef HAVE_GETOPT /* ok, well here's a crude getopt clone I wrote a few years ago. * It's not great, but it's good enough for zmakebas at least. */ int optopt=0,opterr=0,optind=1; char *optarg=NULL; /* holds offset in current argv[] value */ static int optpos=1; /* This routine assumes that the caller is pretty sane and doesn't * try passing an invalid 'optstring' or varying argc/argv. */ int getopt(int argc,char *argv[],char *optstring) { char *ptr; /* check for end of arg list */ if(optind==argc || *(argv[optind])!='-' || strlen(argv[optind])<=1) return(-1); if((ptr=strchr(optstring,argv[optind][optpos]))==NULL) return('?'); /* error: unknown option */ else { optopt=*ptr; if(ptr[1]==':') { if(optind==argc-1) return(':'); /* error: missing option */ optarg=argv[optind+1]; optpos=1; optind+=2; return(optopt); /* return early, avoiding the normal increment */ } } /* now increment position ready for next time. * no checking is done for the end of args yet - this is done on * the next call. */ optpos++; if(optpos>=strlen(argv[optind])) { optpos=1; optind++; } return(optopt); /* return the found option */ } #endif /* !HAVE_GETOPT */ /* dbl2spec() converts a double to an inline-basic-style speccy FP number. * * usage: dbl2spec(num,&exp,&man); * * num is double to convert. * pexp is an int * to where to return the exponent byte. * pman is an unsigned long * to where to return the 4 mantissa bytes. * bit 31 is bit 7 of the 0th (first) mantissa byte, and bit 0 is bit 0 of * the 3rd (last). As such, the unsigned long returned *must* be written * to whatever file in big-endian format to make any sense to a speccy. * * returns 1 if ok, 0 if exponent too big. */ int dbl2spec(double num,int *pexp,unsigned long *pman) { int exp; unsigned long man; /* check for small integers */ if(num==(long)num && num>=-65535.0 && num<=65535.0) { /* ignores sign - see below, which applies to ints too. */ long tmp=(long)fabs(num); exp=0; man=((tmp%256)<<16)|((tmp>>8)<<8); } else { int f; /* It appears that the sign bit is always left as 0 when floating-point * numbers are embedded in programs, and the speccy appears to use the * '-' character to detemine negativity - tests confirm this. * As such, we *completely ignore* the sign of the number. * exp is 0x80+exponent. */ num=fabs(num); /* binary standard form goes from 0.50000... to 0.9999...(dec), like * decimal which goes from 0.10000... to 0.9999.... */ /* as such, if the number is >=1, it gets divided by 2, and exp++. * And if the number is <0.5, it gets multiplied by 2, and exp--. */ exp=0; while(num>=1.0) { num/=2.0; exp++; } while(num<0.5) { num*=2.0; exp--; } /* so now the number is in binary standard form in exp and num. * we check the range of exp... -128 <= exp <= 127. * (if outside, we return error (i.e. 0)) */ if(exp<-128 || exp>127) return(0); exp=128+exp; /* so now all we need to do is roll the bits off the mantissa in `num'. * we start at the 0.5ths bit at bit 0, and shift left 1 each time * round the loop. */ num*=2.0; /* make it so that the 0.5ths bit is the integer part, */ /* and the rest is the fractional (0.xxx) part. */ man=0; for(f=0;f<32;f++) { man<<=1; man|=(int)num; num-=(int)num; num*=2.0; } /* Now, if (int)num is non-zero (well, 1) then we should generally * round up 1. We don't do this if it would cause an overflow in the * mantissa, though. */ if((int)num && man!=0xFFFFFFFF) man++; /* finally, zero out the top bit */ man&=0x7FFFFFFF; } /* done */ *pexp=exp; *pman=man; return(1); } unsigned long grok_hex(unsigned char **ptrp,int textlinenum) { static char *hexits="0123456789abcdefABCDEF",*lookup; unsigned char *ptr=*ptrp; unsigned long v=0,n; /* we know the number starts with "0x" and we're pointing to it */ ptr+=2; if(strchr(hexits,*ptr)==NULL) { fprintf(stderr,"line %d: bad BIN 0x... number\n",textlinenum); exit(1); } while(*ptr && (lookup=strchr(hexits,*ptr))!=NULL) { n=lookup-hexits; if(n>15) n-=6; v=v*16+n; ptr++; } *ptrp=ptr; return(v); } unsigned long grok_binary(unsigned char **ptrp,int textlinenum) { unsigned long v=0; unsigned char *ptr=*ptrp; while(isspace(*ptr)) ptr++; if(*ptr!='0' && *ptr!='1') { fprintf(stderr,"line %d: bad BIN number\n",textlinenum); exit(1); } if(ptr[1]=='x' || ptr[1]=='X') { *ptrp=ptr; return(grok_hex(ptrp,textlinenum)); } while(*ptr=='0' || *ptr=='1') { v*=2; v+=*ptr-'0'; ptr++; } *ptrp=ptr; return(v); } void usage_help() { printf("zmakebas - public domain by Russell Marks.\n\n"); printf("usage: zmakebas [-hlr] [-a line] [-i incr] [-n speccy_filename]\n"); printf(" [-o output_file] [-s line] [input_file]\n\n"); printf(" -a set auto-start line of basic file (default none).\n"); printf(" -h give this usage help.\n"); printf(" -i in labels mode, set line number incr. (default 2).\n"); printf(" -l use labels rather than line numbers.\n"); printf(" -n set Spectrum filename (to be given in tape header)."); printf("\n -o specify output file (default `%s').\n", DEFAULT_OUTPUT); printf(" -r output raw headerless file (default is .tap file).\n"); printf(" -s in labels mode, set starting line number "); printf("(default 10).\n"); } /* cmdline option parsing routine. */ void parse_options(int argc,char *argv[]) { int done=0; done=0; opterr=0; startlabel[0]=0; do switch(getopt(argc,argv,"a:hi:ln:o:rs:")) { case 'a': if(*optarg=='@') if(strlen(optarg+1)>MAX_LABEL_LEN) fprintf(stderr,"Auto-start label too long\n"),exit(1); else strcpy(startlabel,optarg+1); else { startline=(unsigned int)atoi(optarg); if(startline>9999) fprintf(stderr,"Auto-start line must be in the range 0 to 9999.\n"), exit(1); } break; case 'h': /* usage help */ usage_help(); exit(1); case 'i': autoincr=(int)atoi(optarg); /* this is unnecessarily restrictive but keeps things a bit sane */ if(autoincr<1 || autoincr>1000) fprintf(stderr,"Label line incr. must be in the range 1 to 1000.\n"), exit(1); break; case 'l': use_labels=1; break; case 'n': strncpy(speccy_filename,optarg,10); speccy_filename[10]=0; break; case 'o': strcpy(outfile,optarg); break; case 'r': /* output raw file */ output_tap=0; break; case 's': autostart=(int)atoi(optarg); if(autostart<0 || autostart>9999) fprintf(stderr,"Label start line must be in the range 0 to 9999.\n"), exit(1); break; case '?': switch(optopt) { case 'a': fprintf(stderr,"The `a' option takes a line number arg.\n"); break; case 'i': fprintf(stderr,"The `i' option takes a line incr. arg.\n"); break; case 'n': fprintf(stderr,"The `n' option takes a Spectrum filename arg.\n"); break; case 'o': fprintf(stderr,"The `o' option takes a filename arg.\n"); break; case 's': fprintf(stderr,"The `s' option takes a line number arg.\n"); break; default: fprintf(stderr,"Option `%c' not recognised.\n",optopt); } exit(1); case -1: done=1; } while(!done); if(optind1 && fseek(in,0L,SEEK_SET)!=0) { fprintf(stderr,"Need seekable input for label support\n"); exit(1); } while(fgets(buf+1,sizeof(buf)-1,in)!=NULL) { buf[0]=32; /* just in case, for all the ptr[-1] stuff */ textlinenum++; lastline=linenum; if(buf[strlen(buf)-1]=='\n') buf[strlen(buf)-1]=0; /* allow for (shell-style) comments which don't appear in the program, * and also ignore blank lines. */ if(buf[1]==0 || buf[1]=='#') continue; /* check for line continuation */ while(buf[strlen(buf)-1]=='\\') { f=strlen(buf)-1; fgets(buf+f,sizeof(buf)-f,in); textlinenum++; if(buf[strlen(buf)-1]=='\n') buf[strlen(buf)-1]=0; } if(strlen(buf)>=sizeof(buf)-MAX_LABEL_LEN-1) { /* this is nasty, but given how the label substitution works it's * probably the safest thing to do. */ fprintf(stderr,"line %d: line too big for input buffer\n",textlinenum); exit(1); } /* get line number (or assign one) */ if(use_labels) { linestart=buf; /* assign a line number */ linenum+=autoincr; if(linenum>9999) fprintf(stderr,"Generated line number is >9999 - %s\n", (autostart>1 || autoincr>1)?"try using `-s 1 -i 1'" :"too many lines!"), exit(1); } else { ptr=buf; while(isspace(*ptr)) ptr++; if(!isdigit(*ptr)) { fprintf(stderr,"line %d: missing line number\n",textlinenum); exit(1); } linenum=(int)strtol(ptr,(char **)&linestart,10); if(linenum<=lastline) { fprintf(stderr,"line %d: line no. not greater than previous one\n", textlinenum); exit(1); } } if(linenum<0 || linenum>9999) { fprintf(stderr,"line %d: line no. out of range\n",textlinenum); exit(1); } /* lose remaining spaces */ while(isspace(*linestart)) linestart++; /* check there's no line numbers on label-using programs */ if(use_labels && isdigit(*linestart)) { fprintf(stderr,"line %d: line number used in labels mode\n", textlinenum); exit(1); } if(use_labels && *linestart=='@') { if((ptr=strchr(linestart,':'))==NULL) { fprintf(stderr,"line %d: incomplete token definition\n",textlinenum); exit(1); } if(ptr-linestart-1>MAX_LABEL_LEN) { fprintf(stderr,"line %d: token too long\n",textlinenum); exit(1); } if(passnum==1) { *ptr=0; label_lines[labelend]=linenum; strcpy(labels[labelend++],linestart+1); if(labelend>=MAX_LABELS) { fprintf(stderr,"line %d: too many labels\n",textlinenum); exit(1); } for(f=0;f, <=, >=. */ if((*tarrptr)[0]=='<' || (*tarrptr)[1]=='=' || (!isalpha(ptr[-1]) && !isalpha(ptr[toklen]))) { ptr2=linestart+(ptr-lcasebuf); /* the token text is overwritten in the lcase copy too, to * avoid problems with e.g. go to/to. */ *ptr2=*ptr=toknum; for(f=1;f126 || ptr[len]==':')) { unsigned char numbuf[20]; /* this could be optimised to use a single memmove(), but * at least this way it's clear(er) what's happening. */ /* switch text for label. first, remove text */ memmove(ptr-1,ptr+len,strlen(ptr+len)+1); /* make number string */ sprintf(numbuf,"%d",label_lines[f]); len=strlen(numbuf); /* insert room for number string */ ptr--; memmove(ptr+len,ptr,strlen(ptr)+1); memcpy(ptr,numbuf,len); ptr+=len; break; } } if(f==labelend) { fprintf(stderr,"line %d: undefined label\n",textlinenum); exit(1); } } } if(remptr) *remptr=REM_TOKEN_NUM; /* remove 0x01s, deal with backslash things, and add numbers */ ptr=linestart; outptr=outbuf; in_rem=in_quotes=0; while(*ptr) { if(outptr>outbuf+sizeof(outbuf)-10) { fprintf(stderr,"line %d: line too big\n",textlinenum); exit(1); } if(*ptr=='"') in_quotes=!in_quotes; /* as well as 0x01 chars, we skip tabs. */ if(*ptr==1 || *ptr==9 || (!in_quotes && !in_rem && *ptr==' ')) { ptr++; continue; } if(*ptr==REM_TOKEN_NUM) in_rem=1; if(*ptr=='\\') { if(isalpha(ptr[1]) && strchr("VWXYZvwxyz",ptr[1])==NULL) *outptr++=144+tolower(ptr[1])-'a'; else switch(ptr[1]) { case '\\': *outptr++='\\'; break; case '@': *outptr++='@'; break; case '*': *outptr++=127; break; /* copyright symbol */ case '\'': case '.': case ':': case ' ': /* block graphics char */ *outptr++=grok_block(ptr,textlinenum); ptr++; break; case '{': /* directly specify output code */ /* find end of number */ asciiptr=strchr(ptr+2,'}'); if(asciiptr==NULL) { fprintf(stderr, "line %d: unclosed brace in eight-bit character code\n", textlinenum); exit(1); } /* parse number in decimal, octal or hex */ num_ascii=strtoul(ptr+2, NULL, 0); if(num_ascii<0 || num_ascii>255) { fprintf(stderr, "line %d: eight-bit character code out of range\n", textlinenum); exit(1); } *outptr++=(char)num_ascii; /* set pointer to the second char from the end, so we're in the * right place when we skip forward two chars below */ ptr=asciiptr-1; break; default: fprintf(stderr, "line %d: warning: unknown escape `%c', inserting literally\n", textlinenum,ptr[1]); *outptr++=ptr[1]; } ptr+=2; continue; } /* spot any numbers (because we have to add the inline FP * representation). We do this largely by relying on strtod(), * so that we only have to find the start - i.e. a non-alpha char, * an optional `-' or `+', an optional `.', then a digit. */ if(!in_rem && !in_quotes && !isalpha(ptr[-1]) && (isdigit(*ptr) || ((*ptr=='-' || *ptr=='+' || *ptr=='.') && isdigit(ptr[1])) || ((*ptr=='-' || *ptr=='+') && ptr[1]=='.' && isdigit(ptr[2])))) { if(ptr[-1]!=BIN_TOKEN_NUM) /* we have a number. parse with strtod(). */ num=strtod(ptr,(char **)&ptr2); else { /* however, if the number was after a BIN token, the inline * number must match the binary number, e.g. BIN 1001 would be * followed by an inline 9. */ ptr2=ptr; num=(double)grok_binary(&ptr2,textlinenum); } /* output text of number */ memcpy(outptr,ptr,ptr2-ptr); outptr+=ptr2-ptr; *outptr++=0x0e; if(!dbl2spec(num,&num_exp,&num_mantissa)) { fprintf(stderr,"line %d: exponent out of range (number too big)\n", textlinenum); exit(1); } *outptr++=num_exp; *outptr++=(num_mantissa>>24); *outptr++=(num_mantissa>>16); *outptr++=(num_mantissa>>8); *outptr++=(num_mantissa&255); ptr=ptr2; } else { /* if not number, just output char */ *outptr++=*ptr++; } } *outptr++=0x0d; /* add terminating CR */ /* output line */ linelen=outptr-outbuf; if(fileptr+4+linelen>=filebuf+sizeof(filebuf)) { /* the program would be too big to load into a speccy long before * you'd get this error, but FWIW... */ fprintf(stderr,"program too big!\n"); exit(1); } *fileptr++=(linenum>>8); *fileptr++=(linenum&255); *fileptr++=(linelen&255); *fileptr++=(linelen>>8); memcpy(fileptr,outbuf,linelen); fileptr+=linelen; } /* end of pass-making while() */ passnum++; } /* end of do..while() pass loop */ while(use_labels && passnum<=2); if(in!=stdin) fclose(in); /* we only need to do this if outputting a .tap, but might as well do * it to check the label's ok anyway. */ if(*startlabel) { /* this is nearly a can't happen error, but FWIW... */ if(!use_labels) fprintf(stderr,"Auto-start label specified, but not using labels!\n"), exit(1); for(f=0;f>8,chk=255); for(f=0;f # Created: 1993-05-16 # Public domain # $Id: mkinstalldirs,v 1.10 1996/05/03 07:37:52 friedman Exp $ errstatus=0 for file do set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'` shift pathcomp= for d do pathcomp="$pathcomp$d" case "$pathcomp" in -* ) pathcomp=./$pathcomp ;; esac if test ! -d "$pathcomp"; then echo "mkdir $pathcomp" 1>&2 mkdir "$pathcomp" || lasterr=$? if test ! -d "$pathcomp"; then errstatus=$lasterr fi fi pathcomp="$pathcomp/" done done exit $errstatus # mkinstalldirs ends here zmakebas-1.2/demo.bas0100600000175000001440000000422406506056327013320 0ustar rususers# (for Emacs) -*- indented-text -*- # this file `demo.bas' demonstrates the features of zmakebas # (basically just the escape sequences), and gives you an example of # what the input can look like if you use all the bells and whistles. :-) # # See `demolbl.bas' for a label-using version. 10 rem zmakebas demo # tabs (as below) are fine (they're removed) 20 let init= 1000:\ let header= 2000:\ let udgdem= 3000:\ let blockdem=4000 30 gosub init 40 gosub header 50 gosub udgdem 60 gosub blockdem 70 stop # init 1000 for f=usr "a"+7 to usr "u"+7 step 8 1010 poke f,255 1020 next f 1030 let is48=1 # init all attrs just in case 1040 border 7:paper 7:ink 7:bright 0:flash 0:cls # check for 48k speccy or 48k mode. This is a pretty nasty way to do # it, but seems to be the most sane way (from Basic at least). 1050 print "\t" 1060 if screen$(0,0)="S" then let is48=0 1070 ink 0:print at 0,0; 1090 return # header 2000 print ink 5;"\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..";\ "\..\..\..\..\..\..\..\..\..\..\..\..\..\..\.." 2010 print paper 5;" Non-ASCII chars in zmakebas " 2020 print ink 5;"\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''";\ "\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''" 2030 print 2040 return # udgdem 3000 print "Here are the UDGs:"'' 3010 print ink 1;"\a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s"; 3020 if is48 then print ink 1;"\t\u"; 3030 print ''"(They should be underlined.)" 3040 return # blockdem # 01234567890123456789012345678901 4000 print at 11,0;"The block graphics, first as"'\ "listed by a for..next loop, then"'\ "via zmakebas's escape sequences:" 4010 ink 7 4020 print at 15,0; 4030 for f=128 to 143:print chr$(f);" ";:next f:print '' 4040 print at 17,0;"\ \ ' \' \'' \ . \ : \'. \': ";\ "\. \.' \: \:' \.. \.: \:. \::" # draw boxes around them to make it look less confusing 4050 ink 1 4060 for y=0 to 1 4070 for x=0 to 15 4080 plot x*16,55-y*16:draw 7,0:draw 0,-7:draw -7,0:draw 0,7 4090 next x 4100 next y 4110 ink 0 4120 print at 20,0;"And finally here's the copyright"'\ "symbol (";ink 1;"\*";ink 0;\ ") and pound sign (";ink 1;"`";ink 0;")." 4130 return zmakebas-1.2/zmakebas.10100600000175000001440000002072710044745001013554 0ustar rususers.\" -*- nroff -*- .\" .\" zmakebas - convert text file into Spectrum Basic program .\" Public domain by Russell Marks, 1998. .\" .\" zmakebas.1 - man page .\" .TH zmakebas 1 "1st May, 2004" "Version 1.2" "Retrocomputing Tools" .\" .\"------------------------------------------------------------------ .\" .SH NAME zmakebas \- convert text file into Spectrum Basic program .\" .\"------------------------------------------------------------------ .\" .SH SYNOPSIS .PD 0 .B zmakebas .RB [ -hlr ] .RB [ -a .IR startline ] .RB [ -i .IR incr ] .RB [ -n .IR speccy_filename ] .RB [ -o .IR output_file ] .RB [ -s .IR line ] .RI [ input_file ] .P .PD 1 .\" .\"------------------------------------------------------------------ .\" .SH DESCRIPTION zmakebas converts a Spectrum Basic program written as a text file into an actual speccy Basic file (as a .TAP file, or optionally a raw headerless file). By default, input comes from stdin, and output goes to `out.tap'. .PP Using zmakebas rather than (say) writing the Basic in an emulator means you can write using a nicer editor, and can use tools which work on text files, etc. Also, with the `-l' option you can write without line numbers, using labels in their place where necessary. .PP The program was originally intended to be used simply to make little loader programs, so they wouldn't have to be sourceless binaries. However, I went to a fair amount of effort to make sure it'd work for bigger, more serious programs too, so you can also use it for that kind of thing. .\" .\"------------------------------------------------------------------ .\" .SH OPTIONS .TP .I -a make the generated file auto-start from line .IR startline . If `-l' was specified, this can be a label, but don't forget to include the initial `@' to point this out. .TP .I -h give help on command line options. .TP .I -i in labels mode, set line number increment (default 2). .TP .I -l use labels rather than line numbers. .TP .I -n specify filename to use in .TAP file (up to 10 chars), i.e. the filename the speccy will see. Default is a blank filename (10 spaces). .TP .I -o output to .I output_file rather than the default `out.tap'. Use `-' as the filename to output on stdout. .TP .I -r write a raw headerless Basic file, rather than the default .TAP file. .TP .I -s in labels mode, set starting line number (default 10). .\" .\"------------------------------------------------------------------ .\" .SH "INPUT FORMAT" The input should be much as you would type into a speccy (a 128, to be precise), with the following exceptions: .PP Lines starting with `#' are ignored. This allows you to insert comments which are not copied into the output Basic file. .PP Blank lines are ignored. .PP Case is ignored in keywords - `print', `PRINT', and `pRiNt' are equivalent. .PP You can optionally use `randomise' as an alternative to `randomize'. .PP You can get hex numbers by using `bin' with a C-style hex number, e.g. to get 1234h you'd use `bin 0x1234'. (It appears in exactly that form in the speccy listing, though, so don't use it if you want to be able to edit the output program on a speccy.) .PP You can get a pound sign (character 96 on a speccy) by using a backquote (`). .PP One input line normally equals one line of Basic, but you can use backslash as the last character of a line to continue the statement(s) on the next input line. .PP Rather than literally inserting block graphics characters and UDGs as you would on a speccy, you should use an escape sequence. These begin with a backslash (`\\'). To get a UDG, follow this backslash with the UDG's letter, in the range `a' to `u' (`t' and `u' will only have the desired effect if the program is run on a 48k speccy or in 48k mode, though); both upper and lowercase work. To get the copyright symbol, follow it with `*'. To get a block graphics character, follow it with a two-character `drawing' of it using spaces, dots, apostrophes and/or colons. (For example, you'd get character 135 with `\\':', and character 142 with `\\:.'.) To get a literal `@', follow it with `@'. (This is needed only if the `-l' option was given, but works whether it was or not.) To specify a literal eight-bit character code to dump into the Basic output file directly (to use for embedded colour control codes and the like), use braces and a C-syntax number e.g. `\\{42}' for decimal, and `\\{0x42}' for hex. Finally, as usual with such things, you can get a literal backslash by following the first backslash with another. .PP If the `-l' option was given, line numbers must be omitted. Instead these are automatically generated in the output, and you can use labels where necessary as substitute line numbers for `goto' commands etc. A label is defined with the text `@label:' at the beginning of a line (possibly preceded by whitespace). It can be referred to (before or after) with `@label'. Any printable ASCII character other than colon and space can be used in a label name. Here's an example of how labels work, showing both the input and (listing of) the output - first, the input: .PP goto @foo .br print "not seen" .br @foo: print "hello world" .PP Now the output: .PP 10 GO TO 14 .br 12 PRINT "not seen" .br 14 PRINT "hello world" .PP Note that case .I is significant for labels; `foo' and `FOO' are different. .\" .\"------------------------------------------------------------------ .\" .SH BUGS There's almost no syntax checking. To do this would require a complete parser, which would be overkill I think. What's wrong with ``C Nonsense in BASIC'' as a syntax check, anyway? :-) .PP Excess spaces are removed everywhere other than in strings and rem statements. I think this is generally what you'd want, but it could be seen as a bad thing I s'pose. .PP Labels are substituted even in string literals. That's arguably a feature not a bug - the problem is, the label name has to be followed by whitespace or a colon or EOL when referenced, which is fine for more normal references but is less than ideal for references in strings. .PP In the label-using mode, two passes are made over the input, which usually means the input must be from a file. If you like making one-liner Basic programs with `echo' and the like, I'm afraid you'll have to use line numbers. :-) .PP The inline floating-point numbers which have to be generated are not always .I exactly the same as the speccy would generate - but they usually are, and even when they're not the difference is extremely small and due to rounding error on the speccy's part. For example, 0.5 is encoded by the speccy as 7F 7F FF FF FF (exponent -1, mantissa approx. 0.9999999997672) and by zmakebas as 80 00 00 00 00 (exponent 0, mantissa 0.5). .PP zmakebas has most of the same (parsing) problems, relative to the original basic editor, that the 128 editor has. Specifically, you can't use variable names which clash with reserved words, so e.g. `ink ink' doesn't work; and certain tightly-packed constructions you might expect to work, like `chr$a', don't (you need a space or bracket after CHR$). These can be more of a problem with zmakebas though, due to the lack of syntax checking. .PP The way tokenisation is done is sub-optimal, to say the least. If you ran this code on a Z80, even the 128 editor's tokenisation would seem quick in comparison. (Here's a hint of the full horror of it - program lines take exponentially longer to tokenise the longer they are.) However, since I never had a conversion take more than about a second on my old 486 (it took a second for a 10k program), it hardly seems worth the effort of fixing. .PP zmakebas has no problem with translating BIN numbers of more than 16 bits, unlike the speccy, though numbers with more than 32 significant bits can only be approximated, and on machines where `unsigned long' is no more than 32 bits they'll be .I very approximate. :-) (If this sounds confusing, you should note that BIN numbers are translated when entered, and only the 5-byte inline form is dealt with at runtime. This also explains why the speccy tolerates the `bin 0x...' construction.) .PP On machines without FP hardware, zmakebas will be rather slow (this is due to the need to generate inline FP numbers). .PP Since Basic is an acronym, pedants will doubtless insist I should write it as `BASIC'. But we live in a world with `laser' etc., and at least I can be bothered to capitalise the thing, right? :-) .\" .\"------------------------------------------------------------------ .\" .SH "SEE ALSO" .IR fuse "(1)," .IR xz80 "(1)," .IR xzx "(1)" .\" .\"------------------------------------------------------------------ .\" .SH AUTHOR Russell Marks (russell.marks@ntlworld.com). zmakebas-1.2/Makefile0100600000175000001440000000177710044745250013347 0ustar rususers# makefile for zmakebas # comment out the `-DHAVE_GETOPT' if, for some reason or other, you # don't have getopt(). (This is mainly so it'll work on MS-DOG, though # I'm not entirely sure why I bothered supporting that. :-)) # CC=gcc CFLAGS=-O -Wall -DHAVE_GETOPT # these set where the executable and man page are installed PREFIX=/usr/local BINDIR=$(PREFIX)/bin MANDIR=$(PREFIX)/man/man1 all: zmakebas zmakebas: zmakebas.o $(CC) $(CFLAGS) -o zmakebas zmakebas.o installdirs: /bin/sh ./mkinstalldirs $(BINDIR) $(MANDIR) install: zmakebas installdirs install -m 511 zmakebas $(BINDIR) install -m 444 zmakebas.1 $(MANDIR) uninstall: $(RM) $(BINDIR)/zmakebas $(RM) $(MANDIR)/zmakebas.1* clean: $(RM) *~ *.o zmakebas # The stuff below makes the distribution tgz. VERS=1.2 dist: tgz tgz: ../zmakebas-$(VERS).tar.gz ../zmakebas-$(VERS).tar.gz: clean $(RM) ../zmakebas-$(VERS) @cd ..;ln -s zmakebas zmakebas-$(VERS) cd ..;tar zchvf zmakebas-$(VERS).tar.gz zmakebas-$(VERS) @cd ..;$(RM) zmakebas-$(VERS) zmakebas-1.2/ChangeLog0100600000175000001440000000113710100200367013433 0ustar rususers2004-07-23 Russell Marks * Version 1.2. 2004-05-01 Russell Marks * Fixed ignoring of escape sequences in REM statements. Thanks again to Matthew Westcott. * zmakebas.c: added support for embedding literal eight-bit character codes into the output, for e.g. colour control codes. Thanks to Matthew Westcott for the patch. 2000-11-03 Russell Marks * Version 1.1. * zmakebas.1: minor fixes. * Cleaned things up a little, made installation saner, added uninstall target, and updated contact details.