/* bas.c - API for writing standalone programs that deal with tokenized Atari 8-bit BASIC program. */ #include #include #include #include #include #include #include "bas.h" int verbose = 0; unsigned short lomem; unsigned short vntp; unsigned short vntd; unsigned short vvtp; unsigned short stmtab; unsigned short stmcur; unsigned short starp; unsigned short codestart; unsigned short code_end; unsigned short vnstart; unsigned short vvstart; int filelen; const char *self; unsigned char program[BUFSIZE]; FILE *input_file; FILE *output_file; char *output_filename = NULL; void die(const char *msg) { fprintf(stderr, "%s: %s\n", self, msg); exit(1); } void parse_general_args(int argc, char **argv, void (*helpfunc)()) { if(argc < 2) { (*helpfunc)(); exit(1); } if(strcmp(argv[1], "--help") == 0) { (*helpfunc)(); exit(0); } if(strcmp(argv[1], "--version") == 0) { printf("%s %s\n", self, VERSION); exit(0); } } /* read entire file into memory */ void readfile(void) { filelen = fread(program, 1, BUFSIZE - 1, input_file); if(verbose) fprintf(stderr, "Read %d bytes.\n", filelen); if(!feof(input_file)) fprintf(stderr, "Warning: file is >64KB, way too big for a BASIC program.\n"); else if(filelen > MAX_PROG_SIZE) fprintf(stderr, "Warning: file is %d bytes, suspiciously large for a BASIC program.\n", filelen); fclose(input_file); if(filelen < MIN_PROG_SIZE) die("File too short to be a BASIC program (truncated?)\n"); } int writefile(void) { int outbytes; outbytes = fwrite(program, 1, filelen, output_file); fclose(output_file); if(verbose) fprintf(stderr, "Wrote %d bytes.\n", outbytes); return outbytes; } /* get a 16-bit value from the file, in 6502 LSB/MSB order. */ unsigned short getword(int addr) { return program[addr] | (program[addr + 1] << 8); } void setword(int addr, int value) { program[addr] = value & 0xff; program[addr + 1] = value >> 8; } void dump_header_vars(void) { fprintf(stderr, "LOMEM $%04x VNTP $%04x VNTD $%04x VVTP $%04x\n", lomem, vntp, vntd, vvtp); fprintf(stderr, "STMTAB $%04x STMCUR $%04x STARP $%04x\n", stmtab, stmcur, starp); fprintf(stderr, "vnstart $%04x, vvstart $%04x, codestart $%04x, code_end $%04x\n", vnstart, vvstart, codestart, code_end); } void parse_header(void) { lomem = getword(0); vntp = getword(2); vntd = getword(4); vvtp = getword(6); stmtab = getword(8); stmcur = getword(10); starp = getword(12); codestart = stmtab - TBL_OFFSET - (vntp - 256); vnstart = vntp - TBL_OFFSET; vvstart = vvtp - TBL_OFFSET; code_end = starp - TBL_OFFSET; if(lomem) die("This doesn't look like an Atari BASIC program (no $0000 signature)."); if(filelen < code_end) { fprintf(stderr, "Warning: file is truncated: %d bytes, should be %d.\n", filelen, code_end); } if(verbose) dump_header_vars(); } void update_header(void) { setword(0, lomem); setword(2, vntp); setword(4, vntd); setword(6, vvtp); setword(8, stmtab); setword(10, stmcur); setword(12, starp); } /* sometimes the variable name table isn't large enough to hold the generated variable names. move_code() makes more space, by moving the rest of the program (including the variable value table) up in memory. */ void move_code(int offset) { unsigned char *dest = program + vvstart + offset; if(dest < program || ((filelen + offset) > (BUFSIZE - 1))) { die("Attempt to move memory out of range; corrupt header bytes?\n"); } memmove(dest, program + vvstart, filelen); vntd += offset; vvtp += offset; stmtab += offset; stmcur += offset; starp += offset; filelen += offset; update_header(); parse_header(); } void adjust_vntable_size(int oldsize, int newsize) { int move_by; if(oldsize != newsize) { move_by = newsize - oldsize; if(verbose) fprintf(stderr, "Need %d bytes for vntable, have %d, moving VVTP by %d to $%04x.\n", newsize, oldsize, move_by, vvtp + move_by); move_code(move_by); } } unsigned char get_vartype(unsigned char tok) { return program[vvstart + (tok & 0x7f) * 8] >> 6; } /* return true if the variable name table is OK */ int vntable_ok(void) { int vp, bad; if(vntp == vntd) { if(verbose) fprintf(stderr, "No variables.\n"); return 1; } /* first pass: bad = 1 if all the bytes in the table have the same value, no matter what it is. */ vp = vnstart + 1; bad = 1; while(vp < vvstart - 1) { if(program[vp] != program[vnstart]) { bad = 0; break; } vp++; } if(bad) return 0; /* 2nd pass: bad = 1 if there's any invalid character in the table. */ vp = vnstart; while(vp < vvstart) { unsigned char c = program[vp]; /* treat a null byte as end-of-table, ignore any junk between it and VNTP. */ if(c == 0) break; vp++; /* inverse $ or ( is OK */ if(c == 0xa4 || c == 0xa8) continue; /* numbers and letters are allowed, inverse or normal. */ c &= 0x7f; if(c >= 0x30 && c <= 0x39) continue; if(c >= 0x41 && c <= 0x5a) continue; bad++; break; } return !bad; } void invalid_args(const char *arg) { fprintf(stderr, "%s: Invalid argument '%s'.\n\n", self, arg); exit(1); } FILE *open_file(const char *name, const char *mode) { FILE *fp; if(!(fp = fopen(name, mode))) { perror(name); exit(1); } return fp; } void open_input(const char *name) { if(!name || strcmp(name, "-") == 0) { if(isatty(fileno(stdin))) { die("Can't read binary data from the terminal."); } if(freopen(NULL, "rb", stdin)) { input_file = stdin; return; } else { perror("stdin"); exit(1); } } input_file = open_file(name, "rb"); } void open_output(const char *name) { if(!name || (strcmp(name, "-") == 0)) { if(isatty(fileno(stdout))) { die("Refusing to write binary data to the terminal."); } if(freopen(NULL, "wb", stdout)) { output_file = stdout; return; } else { perror("stdout"); exit(1); } } output_file = open_file(name, "wb"); } void set_self(const char *argv0) { char *p; self = argv0; p = strrchr(self, '/'); if(p) self = p + 1; } /* callbacks */ CALLBACK_PTR(on_start_line); CALLBACK_PTR(on_bad_line_length); CALLBACK_PTR(on_end_line); CALLBACK_PTR(on_start_stmt); CALLBACK_PTR(on_end_stmt); CALLBACK_PTR(on_cmd_token); CALLBACK_PTR(on_text); CALLBACK_PTR(on_exp_token); CALLBACK_PTR(on_var_token); CALLBACK_PTR(on_string_const); CALLBACK_PTR(on_num_const); #define CALL(x) if(x) (*x)(lineno, pos, program[pos], end) void walk_code(unsigned int startlineno, unsigned int endlineno) { int linepos, nextpos, offset, soffset, lineno, pos, end, tok; linepos = codestart; while(linepos < filelen) { /* loop over lines */ lineno = getword(linepos); offset = program[linepos + 2]; nextpos = linepos + offset; end = nextpos; pos = linepos; if(offset < 6) { CALL(on_bad_line_length); offset = program[linepos + 2]; /* on_bad_line_length fixed it (we hope) */ if(offset < 6) die("Program is code-protected; unprotect it first."); } if(lineno < startlineno) { linepos = nextpos; continue; } CALL(on_start_line); pos = linepos + 3; while(pos < nextpos) { /* loop over statements within a line */ soffset = program[pos]; end = linepos + soffset; CALL(on_start_stmt); while(pos < end) { /* loop over tokens within a statement */ pos++; CALL(on_cmd_token); switch(program[pos]) { case CMD_REM: case CMD_DATA: case CMD_ERROR: pos++; CALL(on_text); pos = end; break; default: pos++; break; } while(pos < end) { /* loop over operators */ tok = program[pos]; switch(tok) { case OP_NUMCONST: CALL(on_exp_token); pos++; CALL(on_num_const); pos += 6; break; case OP_STRCONST: CALL(on_exp_token); pos++; CALL(on_string_const); pos += program[pos] + 1; break; default: if(tok & 0x80) { CALL(on_var_token); } else { CALL(on_exp_token); } pos++; break; } } CALL(on_end_stmt); } } CALL(on_end_line); if(lineno >= endlineno) break; linepos = nextpos; } }