zmakebas-1.2b/0000755000175100001440000000000014160451671012051 5ustar rususerszmakebas-1.2b/zmakebas.10000644000175100001440000002065414157510102013726 0ustar rususers.\" -*- nroff -*- .\" .\" zmakebas - convert text file into Spectrum Basic program .\" Public domain by Russell Marks, 1998. .\" .\" zmakebas.1 - man page .\" .TH zmakebas 1 "19th December, 2021" "Version 1.2b" "Retrocomputing Tools" .\" .\"------------------------------------------------------------------ .\" .SH NAME zmakebas \- convert text file into Spectrum Basic program .\" .\"------------------------------------------------------------------ .\" .SH SYNOPSIS .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 ] .PP .\" .\"------------------------------------------------------------------ .\" .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 .B -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 .B -h give help on command line options. .TP .B -i in labels mode, set line number increment (default 2). .TP .B -l use labels rather than line numbers. .TP .B -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 .B -o output to .I output_file rather than the default `out.tap'. Use `-' as the filename to output on stdout. .TP .B -r write a raw headerless Basic file, rather than the default .TAP file. .TP .B -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" .BR fuse "(1)" .\" .\"------------------------------------------------------------------ .\" .SH AUTHOR Russell Marks (zgedneil@gmail.com). zmakebas-1.2b/zmakebas.c0000644000175100001440000006717214157510250014022 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 #define VAL_TOKEN_NUM 176 #define VALSTR_TOKEN_NUM 174 #define DEFFN_TOKEN_NUM 206 /* 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. * * One exception to the above - VAL/VAL$ positions are flipped here * so that we do VAL$ first, and swapped after tokenising. */ 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 1.2b - 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': if(strlen(optarg)>sizeof(outfile)-1) fprintf(stderr,"Filename too long\n"),exit(1); else 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(optindsizeof(infile)-1) fprintf(stderr,"Filename too long\n"),exit(1); else strcpy(infile,argv[optind]); } } int grok_block(unsigned char *ptr,int textlinenum) { static char *lookup[]= { " ", " '", "' ", "''", " .", " :", "'.", "':", ". ", ".'", ": ", ":'", "..", ".:", ":.", "::", NULL }; char **lptr; int f=128,v=-1; for(lptr=lookup;*lptr!=NULL;lptr++,f++) { if(strncmp(ptr+1,*lptr,2)==0) { v=f; break; } } if(v==-1) { fprintf(stderr,"line %d: invalid block graphics escape\n",textlinenum); exit(1); } return(v); } int main(int argc,char *argv[]) { #ifdef MSDOS static unsigned char buf[512],lcasebuf[512],outbuf[1024]; #else /* deliberately very large buffers, to allow e.g. embedding of m/c * programs in REM statements. 48k*8 should cover all eventualities, * and not generally be a problem at this point. I mean, it's not * even a megabyte in total, what's that between friends? :-) * * Note that these need to be large even if line-continuation * backslashes are used, as the line is constructed in buf[]. outbuf * is in the Spectrum's tokenised format, and can be much smaller. */ static unsigned char buf[8*49152],lcasebuf[8*49152]; static unsigned char outbuf[49152]; #endif int f,toknum,toklen,linenum,linelen,in_quotes,in_rem,in_deffn,lastline; char **tarrptr; unsigned char *ptr,*ptr2,*linestart,*outptr,*remptr,*fileptr,*asciiptr; double num; int num_exp; unsigned long num_mantissa,num_ascii; int textlinenum; int chk=0; int alttok; int passnum=1; FILE *in=stdin,*out=stdout; strcpy(speccy_filename,""); strcpy(infile,"-"); strcpy(outfile,DEFAULT_OUTPUT); parse_options(argc,argv); if(strcmp(infile,"-")!=0 && (in=fopen(infile,"r"))==NULL) fprintf(stderr,"Couldn't open input file.\n"),exit(1); fileptr=filebuf; linenum=-1; /* to set lastline */ /* we make one pass if using line numbers, two if using labels */ do { if(use_labels) linenum=autostart-autoincr; textlinenum=0; if(passnum>1 && 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; the "*buf" checks here aren't strictly * necessary (see buf[0] above) but I'm just happier this way. :-) */ while(*buf && buf[strlen(buf)-1]=='\\' && !feof(in)) { f=strlen(buf)-1; if(fgets(buf+f,sizeof(buf)-f,in)) textlinenum++; else { if(*buf) buf[strlen(buf)-1]=0; /* remove backslash on EOF */ } if(*buf && 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]==':')) { static unsigned char numbuf[64]; /* 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_deffn=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==DEFFN_TOKEN_NUM) in_deffn=1; 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 { /* special def fn case */ if(in_deffn) { if(*ptr=='=') in_deffn=0; else { if(*ptr==',' || *ptr==')') { *outptr++=0x0e; *outptr++=0; *outptr++=0; *outptr++=0; *outptr++=0; *outptr++=0; *outptr++=*ptr++; } if(*ptr!=' ') { if(*ptr=='=') in_deffn=0; *outptr++=*ptr++; } } } 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 * Version 1.2b. * bugs12.bas: fixed incorrect comment about LIST result, it was only version 1.2a that had the relevant issue. 2021-12-19 Russell Marks * README: slight update for new version. * zmakebas.1: version bump. * bugs12.bas: updated to add a test of sorts for the latest DEF FN bugfix. * zmakebas.c: fix for failing to spot the end of DEF FN parameters. Thanks to Chris Born for reporting this bug. 2021-07-04 Russell Marks * Version 1.2a. * zmakebas.c: fixed possible buffer overruns. The remaining uses of strcpy look reasonable. There is one use of sprintf I'd prefer to replace with snprintf, but for some platforms it might be unhelpful to require that. It's only used to get a known-4-digit line number, so realistically it should be fine. 2021-07-03 Russell Marks * README: small tweaks, and noting that other versions are around. * Makefile: updated to avoid warnings on more recent gcc, and use a more usual man page location and file permissions. * zmakebas.1: minor tweaks to update it, and made option listing more consistent. * bugs12.bas: tests for various bugs in 1.2, which are now fixed. * Fixed line-continuation bug, it got confused if it hit EOF in certain circumstances and could loop indefinitely. * Made input buffer sizes capable of handling Basic lines larger than a 48k Spectrum can load. So if you want to embed substantial binary data like machine code in a REM statement you should now be able to. Thanks to Paul Humphreys for his version that suggested this would be useful, though I went a bit bigger. :-) * Included DEF FN fix based on Antonio Villena's version. I altered it slightly to avoid the sequence-point issue. 2021-06-27 Russell Marks * Fixed non-parsing of VAL$. Thanks to Andy J for pointing this out. 2004-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. zmakebas-1.2b/mkinstalldirs0000644000175100001440000000133407016274623014657 0ustar rususers#! /bin/sh # mkinstalldirs --- make directory hierarchy # Author: Noah Friedman # 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