diff options
Diffstat (limited to 'bas.c')
-rw-r--r-- | bas.c | 375 |
1 files changed, 375 insertions, 0 deletions
@@ -0,0 +1,375 @@ +/* bas.c - API for writing standalone programs that deal with + tokenized Atari 8-bit BASIC program. */ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <time.h> + +#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); +CALLBACK_PTR(on_trailing_garbage); + +#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 = -1, tmpno, pos, end, tok; + + linepos = codestart; + while(linepos < filelen) { /* loop over lines */ + tmpno = getword(linepos); + if(tmpno <= lineno) { + fprintf(stderr, "Warning: line number %d at offset $%04x is <= previous line number %d.\n", + tmpno, linepos, lineno); + } + lineno = tmpno; + 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("Fatal: Program is code-protected; unprotect it first."); + } + + if(lineno < startlineno) { + linepos = nextpos; + continue; + } + + if(lineno > endlineno) break; + + 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); + + linepos = nextpos; + if(lineno == 32768) break; + } + + if(endlineno == 32768 && linepos < filelen) { + if(verbose) + fprintf(stderr, "%s: Trailing garbage at EOF, %d bytes.\n", self, filelen - linepos); + CALL(on_trailing_garbage); + } +} |