apple2a/main.c

663 lines
16 KiB
C
Raw Normal View History

2018-07-31 19:02:26 +00:00
#include "platform.h"
uint8_t *title = "Apple IIa";
uint8_t title_length = 9;
2018-07-28 05:30:44 +00:00
2018-07-31 19:48:29 +00:00
#define CURSOR_GLYPH 127
#define SCREEN_WIDTH 40
#define SCREEN_STRIDE (3*SCREEN_WIDTH + 8)
2018-08-02 06:29:37 +00:00
// 6502 instructions.
#define I_JSR 0x20
#define I_RTS 0x60
#define I_LDX 0xA2
#define I_LDA 0xA9
// Tokens.
2018-08-01 17:44:24 +00:00
#define T_HOME 0x80
#define T_PRINT 0x81
2018-08-01 20:34:26 +00:00
#define T_LIST 0x82
2018-08-01 17:44:24 +00:00
// List of tokens. The token value is the index plus 0x80.
static uint8_t *TOKEN[] = {
2018-08-01 17:44:24 +00:00
"HOME",
"PRINT",
2018-08-01 20:34:26 +00:00
"LIST",
2018-08-01 17:44:24 +00:00
};
static int16_t TOKEN_COUNT = sizeof(TOKEN)/sizeof(TOKEN[0]);
2018-08-01 17:44:24 +00:00
2018-07-31 19:48:29 +00:00
// Location of cursor in logical screen space.
uint16_t g_cursor_x = 0;
uint16_t g_cursor_y = 0;
2018-07-31 19:48:29 +00:00
// Whether the cursor is being displayed.
uint16_t g_showing_cursor = 0;
2018-07-31 19:48:29 +00:00
// Character at the cursor location.
uint8_t g_cursor_ch = 0;
uint8_t g_input_buffer[40];
int16_t g_input_buffer_length = 0;
2018-07-31 22:03:06 +00:00
// Compiled binary.
uint8_t g_compiled[128];
int16_t g_compiled_length = 0;
2018-08-01 17:58:03 +00:00
void (*g_compiled_function)() = (void (*)()) g_compiled;
2018-07-31 19:48:29 +00:00
2018-08-01 20:34:26 +00:00
// Stored program. Each line is:
// - Two bytes for pointer to next line (or zero if none).
// - Two bytes for line number.
// - Program line.
// - Nul.
uint8_t g_program[1024];
2018-08-01 20:34:26 +00:00
2018-07-31 19:48:29 +00:00
/**
* Return the memory location of the cursor.
*/
static volatile uint8_t *cursor_pos() {
int16_t block = g_cursor_y >> 3;
int16_t line = g_cursor_y & 0x07;
2018-07-31 19:48:29 +00:00
2018-08-01 17:58:03 +00:00
return TEXT_PAGE1_BASE + line*SCREEN_STRIDE + block*SCREEN_WIDTH + g_cursor_x;
2018-07-31 19:48:29 +00:00
}
/**
* Shows the cursor. Safe to call if it's already showing.
*/
static void show_cursor() {
2018-08-01 17:58:03 +00:00
if (!g_showing_cursor) {
volatile uint8_t *pos = cursor_pos();
2018-08-01 17:58:03 +00:00
g_cursor_ch = *pos;
2018-07-31 19:48:29 +00:00
*pos = CURSOR_GLYPH | 0x80;
2018-08-01 17:58:03 +00:00
g_showing_cursor = 1;
2018-07-31 19:48:29 +00:00
}
}
/**
* Hides the cursor. Safe to call if it's not already shown.
*/
static void hide_cursor() {
2018-08-01 17:58:03 +00:00
if (g_showing_cursor) {
volatile uint8_t *pos = cursor_pos();
2018-08-01 17:58:03 +00:00
*pos = g_cursor_ch;
g_showing_cursor = 0;
2018-07-31 19:48:29 +00:00
}
}
/**
* Moves the cursor to the specified location, where X
* is 0 to 39 inclusive, Y is 0 to 23 inclusive.
*/
static void move_cursor(int16_t x, int16_t y) {
2018-07-31 19:48:29 +00:00
hide_cursor();
2018-08-01 17:58:03 +00:00
g_cursor_x = x;
g_cursor_y = y;
2018-07-31 19:48:29 +00:00
}
2018-07-31 22:03:06 +00:00
/**
* Clear the screen with non-reversed spaces.
*/
static void home() {
volatile uint8_t *p = TEXT_PAGE1_BASE;
uint8_t ch = ' ' | 0x80;
int16_t i;
2018-07-31 22:03:06 +00:00
// TODO: Could write these as words, not chars.
for (i = SCREEN_STRIDE*8; i >= 0; i--) {
*p++ = ch;
}
move_cursor(0, 0);
}
/**
2018-08-01 17:44:24 +00:00
* Prints the character and advances the cursor. Handles newlines.
2018-07-31 22:03:06 +00:00
*/
static void print_char(uint8_t c) {
volatile uint8_t *loc = cursor_pos();
2018-07-31 22:03:06 +00:00
2018-08-01 17:44:24 +00:00
if (c == '\n') {
// TODO: Scroll.
2018-08-01 17:58:03 +00:00
move_cursor(0, g_cursor_y + 1);
2018-08-01 17:44:24 +00:00
} else {
// Print character.
*loc = c | 0x80;
2018-08-01 17:58:03 +00:00
move_cursor(g_cursor_x + 1, g_cursor_y);
2018-08-01 17:44:24 +00:00
}
}
/**
* Print a string at the cursor.
*/
static void print(uint8_t *s) {
2018-07-31 22:03:06 +00:00
while (*s != '\0') {
2018-08-01 17:44:24 +00:00
print_char(*s++);
2018-07-31 22:03:06 +00:00
}
}
2018-08-01 20:34:26 +00:00
/**
* Print an unsigned integer.
2018-08-01 20:34:26 +00:00
*/
static void print_int(uint16_t i) {
2018-08-01 20:34:26 +00:00
// Is this the best way to do this? I've seen it done backwards, where
// digits are added to a buffer least significant digit first, then reversed,
// but this seems faster.
if (i >= 10000) {
int16_t r = i / 10000;
2018-08-01 20:34:26 +00:00
print_char('0' + r);
i -= r*10000;
}
if (i >= 1000) {
int16_t r = i / 1000;
2018-08-01 20:34:26 +00:00
print_char('0' + r);
i -= r*1000;
}
if (i >= 100) {
int16_t r = i / 100;
2018-08-01 20:34:26 +00:00
print_char('0' + r);
i -= r*100;
}
if (i >= 10) {
int16_t r = i / 10;
2018-08-01 20:34:26 +00:00
print_char('0' + r);
i -= r*10;
}
print_char('0' + i);
}
/**
* Copy a memory buffer. Source and destination may overlap.
2018-08-01 20:34:26 +00:00
*/
static void memmove(uint8_t *dest, uint8_t *src, uint16_t count) {
// See if we overlap.
if (dest > src && dest < src + count) {
// Overlapping with src before dest, we have to copy backward.
dest += count;
src += count;
while (count-- > 0) {
*--dest = *--src;
}
} else {
// No overlap, or dest before src, which is fine.
while (count-- > 0) {
*dest++ = *src++;
}
2018-08-01 20:34:26 +00:00
}
}
/**
* Get the length of a nul-terminated string.
*/
static int16_t strlen(uint8_t *s) {
uint8_t *original = s;
2018-08-01 20:34:26 +00:00
while (*s != '\0') {
s += 1;
}
return s - original;
}
/**
* Print the tokenized string, with tokens displayed as their full text.
* Prints a newline at the end.
*/
static void print_detokenized(uint8_t *s) {
2018-08-01 20:34:26 +00:00
while (*s != '\0') {
if (*s >= 0x80) {
print_char(' ');
print(TOKEN[*s - 0x80]);
print_char(' ');
} else {
print_char(*s);
}
s += 1;
}
print_char('\n');
}
/**
* Get the pointer to the next line in the stored program. Returns 0
* if we're at the end.
*/
static uint8_t *get_next_line(uint8_t *line) {
return *((uint8_t **) line);
2018-08-01 20:34:26 +00:00
}
/**
* Get the line number of a stored program line.
*/
static uint16_t get_line_number(uint8_t *line) {
return *((uint16_t *) (line + 2));
2018-08-01 20:34:26 +00:00
}
/**
* Return a pointer to the end of the program. This is one byte PAST the
* last bytes in the program, which are two nuls. The "line" parameter is
2018-08-01 20:34:26 +00:00
* an optional starting point, to use as an optimization instead of starting
* from the beginning.
*/
static uint8_t *get_end_of_program(uint8_t *line) {
uint8_t *next_line;
2018-08-01 20:34:26 +00:00
if (line == 0) {
// Start at the beginning if not specified.
line = g_program;
2018-08-01 20:34:26 +00:00
}
while ((next_line = get_next_line(line)) != 0) {
2018-08-01 20:34:26 +00:00
line = next_line;
}
// Skip the null "next" pointer.
return line + 2;
2018-08-01 20:34:26 +00:00
}
2018-08-02 06:29:37 +00:00
/**
* Print a single newline.
*/
static void print_newline() {
print_char('\n');
2018-08-01 06:19:15 +00:00
}
2018-08-01 20:34:26 +00:00
/**
* List the stored program.
*/
static void list_statement() {
uint8_t *line = g_program;
uint8_t *next_line;
2018-08-01 20:34:26 +00:00
while ((next_line = get_next_line(line)) != 0) {
2018-08-01 20:34:26 +00:00
print_int(get_line_number(line));
print_char(' ');
print_detokenized(line + 4);
line = next_line;
2018-08-01 20:34:26 +00:00
}
}
2018-07-31 22:03:06 +00:00
/**
* If a starts with string b, returns the position in a after b. Else returns null.
2018-07-31 22:03:06 +00:00
*/
static uint8_t *skip_over(uint8_t *a, uint8_t *b) {
while (*a != '\0' && *b != '\0') {
2018-07-31 22:03:06 +00:00
if (*a != *b) {
// Doesn't start with b.
2018-07-31 22:03:06 +00:00
return 0;
}
2018-07-28 05:30:44 +00:00
2018-07-31 22:03:06 +00:00
a += 1;
b += 1;
}
2018-07-28 05:30:44 +00:00
// See if we're at the end of b.
return *b == '\0' ? a : 0;
2018-07-31 22:03:06 +00:00
}
/**
* Display a syntax error message.
*/
static void syntax_error() {
print("\n?SYNTAX ERROR");
// No linefeed, assume prompt will do it.
2018-07-31 22:03:06 +00:00
}
/**
2018-08-01 17:56:48 +00:00
* Add a function call to the compiled buffer.
2018-07-31 22:03:06 +00:00
*/
2018-08-02 06:29:37 +00:00
static void add_call(void *function) {
uint16_t addr = (int16_t) function;
2018-07-31 22:03:06 +00:00
2018-08-02 06:29:37 +00:00
g_compiled[g_compiled_length++] = I_JSR;
2018-08-01 17:58:03 +00:00
g_compiled[g_compiled_length++] = addr & 0xFF;
g_compiled[g_compiled_length++] = addr >> 8;
2018-07-31 22:03:06 +00:00
}
/**
2018-08-01 17:56:48 +00:00
* Add a function return to the compiled buffer.
2018-07-31 22:03:06 +00:00
*/
static void add_return() {
2018-08-02 06:29:37 +00:00
g_compiled[g_compiled_length++] = I_RTS;
2018-07-31 22:03:06 +00:00
}
/**
* Advance s over whitespace, which is just a space, returning
* the new pointer.
*/
2018-08-02 06:29:37 +00:00
/* Unused.
static uint8_t *skip_whitespace(uint8_t *s) {
while (*s == ' ') {
s += 1;
}
return s;
}
2018-08-02 06:29:37 +00:00
*/
/**
* Parse an unsigned integer, returning the value and moving the pointer
* past the end of the number. The pointer must already be at the beginning
* of the number.
*/
static uint16_t parse_uint16(uint8_t **s_ptr) {
uint16_t value = 0;
uint8_t *s = *s_ptr;
while (*s >= '0' && *s <= '9') {
value = value*10 + (*s - '0');
s += 1;
}
*s_ptr = s;
return value;
}
2018-08-01 17:44:24 +00:00
/**
* Tokenize a string in place. Returns (and removes) any line number, or 0xFFFF
* if there's none.
2018-08-01 17:44:24 +00:00
*/
static uint16_t tokenize(uint8_t *s) {
uint8_t *t = s; // Tokenized version.
int16_t line_number;
2018-08-01 17:44:24 +00:00
// Parse optional line number.
if (*s >= '0' && *s <= '9') {
2018-08-02 06:29:37 +00:00
line_number = parse_uint16(&s);
2018-08-01 17:44:24 +00:00
} else {
line_number = 0xFFFF;
}
// Convert tokens.
while (*s != '\0') {
if (*s == ' ') {
// Skip spaces.
s++;
} else {
int16_t i;
uint8_t *skipped = 0;
2018-08-01 17:44:24 +00:00
for (i = 0; i < TOKEN_COUNT; i++) {
skipped = skip_over(s, TOKEN[i]);
if (skipped != 0) {
// Record token.
*t++ = 0x80 + i;
s = skipped;
break;
}
}
if (skipped == 0) {
// Didn't find a token, just copy text.
*t++ = *s++;
}
}
}
// Terminate string.
2018-08-01 17:44:24 +00:00
*t++ = '\0';
return line_number;
}
/**
* Find the stored program line with the given line number. If the line does
* not exist, returns a pointer to the location where it should be inserted.
2018-08-01 17:44:24 +00:00
*/
static uint8_t *find_line(uint16_t line_number) {
uint8_t *line = g_program;
uint8_t *next_line;
2018-08-01 20:34:26 +00:00
while ((next_line = get_next_line(line)) != 0) {
// See if we hit it or just blew past it.
if (get_line_number(line) >= line_number) {
break;
2018-08-01 20:34:26 +00:00
}
line = next_line;
}
return line;
2018-08-01 17:44:24 +00:00
}
2018-07-31 22:03:06 +00:00
/**
* Process the user's line of input, possibly compiling the code.
* and executing it.
*/
static void process_input_buffer() {
uint8_t *s; // Where we are in the buffer.
int8_t done;
uint16_t line_number;
2018-08-01 17:58:03 +00:00
g_input_buffer[g_input_buffer_length] = '\0';
2018-08-01 17:44:24 +00:00
// Tokenize in-place.
2018-08-01 17:58:03 +00:00
line_number = tokenize(g_input_buffer);
2018-08-01 20:34:26 +00:00
if (line_number == 0xFFFF) {
// Immediate mode.
s = g_input_buffer;
2018-08-01 17:44:24 +00:00
2018-08-01 20:34:26 +00:00
// Compile the line of BASIC.
g_compiled_length = 0;
2018-07-31 22:03:06 +00:00
2018-08-01 20:34:26 +00:00
do {
int8_t error = 0;
2018-08-01 20:34:26 +00:00
// Default to being done after one statement.
done = 1;
2018-08-01 20:34:26 +00:00
if (*s == '\0' || *s == ':') {
// Empty statement.
} else if (*s == T_HOME) {
s += 1;
add_call(home);
} else if (*s == T_PRINT) {
s += 1;
2018-08-02 06:29:37 +00:00
if (*s >= '0' && *s <= '9') {
// Parse expression.
uint16_t value = parse_uint16(&s);
g_compiled[g_compiled_length++] = I_LDX;
g_compiled[g_compiled_length++] = value >> 8;
g_compiled[g_compiled_length++] = I_LDA;
g_compiled[g_compiled_length++] = value & 0xFF;
add_call(print_int);
}
add_call(print_newline);
2018-08-01 20:34:26 +00:00
} else if (*s == T_LIST) {
s += 1;
add_call(list_statement);
} else {
error = 1;
}
2018-08-01 20:34:26 +00:00
// Now we're at the end of our statement.
if (!error) {
if (*s == ':') {
// Skip colon.
s += 1;
// Next statement.
done = 0;
} else if (*s != '\0') {
// Junk at the end of the statement.
error = 1;
}
}
2018-07-31 22:03:06 +00:00
2018-08-01 20:34:26 +00:00
if (error) {
add_call(syntax_error);
}
} while (!done);
// Return from function.
add_return();
2018-07-31 22:03:06 +00:00
2018-08-01 20:34:26 +00:00
if (g_compiled_length > sizeof(g_compiled)) {
// TODO: Check while adding bytes, not at the end.
print("\n?Binary length exceeded");
} else {
// Call it.
g_compiled_function();
}
} else {
// Stored mode. Add line to program.
// Return line to replace or delete, or location to insert new line.
uint8_t *line = find_line(line_number);
uint8_t *next_line = get_next_line(line);
uint8_t *end_of_program = get_end_of_program(line);
int16_t adjustment = 0;
2018-08-01 20:34:26 +00:00
if (next_line == 0 || get_line_number(line) != line_number) {
// Didn't find line. Insert it here.
2018-08-01 20:34:26 +00:00
// Next pointer, line number, line, and nul.
uint8_t buffer_length = strlen(g_input_buffer);
adjustment = 4 + buffer_length + 1;
// Shift rest of program over.
memmove(line + adjustment, line, end_of_program - line);
// Next line. Point to yourself initially, we'll adjust below.
*((uint8_t **) line) = line;
2018-08-01 20:34:26 +00:00
// Line number.
*((uint16_t *) (line + 2)) = line_number;
// Buffer and nul.
memmove(line + 4, g_input_buffer, buffer_length + 1);
2018-08-01 20:34:26 +00:00
} else {
// Found line.
if (g_input_buffer[0] == '\0') {
// Empty line, delete old one.
// Adjustment is negative.
adjustment = line - next_line;
memmove(line, next_line, end_of_program - next_line);
2018-08-01 20:34:26 +00:00
} else {
// Replace line.
// Compute adjustment.
uint8_t buffer_length = strlen(g_input_buffer);
adjustment = line - next_line + 4 + buffer_length + 1;
memmove(next_line + adjustment, next_line, end_of_program - next_line);
// Buffer and nul.
memmove(line + 4, g_input_buffer, buffer_length + 1);
}
}
if (adjustment != 0) {
// Adjust all the next pointers.
while ((next_line = get_next_line(line)) != 0) {
// Adjust by the amount we inserted or deleted.
next_line += adjustment;
*((uint8_t **) line) = next_line;
line = next_line;
2018-08-01 20:34:26 +00:00
}
}
}
2018-07-31 22:03:06 +00:00
}
int16_t main(void)
2018-07-31 22:03:06 +00:00
{
int16_t blink;
2018-07-28 05:30:44 +00:00
2018-08-02 06:29:37 +00:00
/*
int16_t i, j, k;
i = 2;
j = 3;
k = 4;
i = i*j;
*/
// Blank program.
g_program[0] = '\0';
g_program[1] = '\0';
2018-08-01 20:34:26 +00:00
// Initialize UI.
2018-07-31 07:05:22 +00:00
home();
2018-07-28 05:30:44 +00:00
2018-07-31 20:42:21 +00:00
// Display the character set.
2018-08-02 06:29:37 +00:00
/*
if (1) {
int16_t i;
2018-08-01 20:34:26 +00:00
for (i = 0; i < 256; i++) {
volatile uint8_t *loc;
2018-08-01 20:34:26 +00:00
// Fails with: unhandled instruction B2
move_cursor(i % 16, i >> 4);
// Works.
// move_cursor(i & 0x0F, i >> 4);
loc = cursor_pos();
*loc = i;
}
while(1);
2018-07-31 20:42:21 +00:00
}
2018-08-02 06:29:37 +00:00
*/
2018-07-31 20:42:21 +00:00
2018-08-01 20:34:26 +00:00
// Print title.
2018-07-31 22:03:06 +00:00
move_cursor((40 - title_length) / 2, 0);
print(title);
2018-07-28 05:30:44 +00:00
2018-07-31 07:05:22 +00:00
// Prompt.
2018-07-31 22:03:06 +00:00
print("\n\n]");
2018-07-31 07:05:22 +00:00
2018-07-31 19:48:29 +00:00
// Keyboard input.
2018-08-01 20:34:26 +00:00
blink = 0;
2018-08-01 17:58:03 +00:00
g_input_buffer_length = 0;
2018-07-31 19:48:29 +00:00
show_cursor();
2018-07-31 07:05:22 +00:00
while(1) {
2018-07-31 19:48:29 +00:00
// Blink cursor.
2018-08-01 20:34:26 +00:00
blink += 1;
if (blink == 3000) {
2018-08-01 17:58:03 +00:00
if (g_showing_cursor) {
2018-07-31 19:48:29 +00:00
hide_cursor();
} else {
show_cursor();
}
2018-08-01 20:34:26 +00:00
blink = 0;
2018-07-31 19:48:29 +00:00
}
if(keyboard_test()) {
hide_cursor();
while(keyboard_test()) {
uint8_t key;
2018-07-31 19:02:26 +00:00
2018-07-31 19:48:29 +00:00
key = keyboard_get();
if (key == 8) {
// Backspace.
2018-08-01 17:58:03 +00:00
if (g_input_buffer_length > 0) {
move_cursor(g_cursor_x - 1, g_cursor_y);
g_input_buffer_length -= 1;
2018-07-31 19:48:29 +00:00
}
} else if (key == 13) {
// Return.
2018-08-01 17:58:03 +00:00
move_cursor(0, g_cursor_y + 1);
2018-07-31 22:03:06 +00:00
process_input_buffer();
print("\n]");
2018-08-01 17:58:03 +00:00
g_input_buffer_length = 0;
2018-07-31 19:48:29 +00:00
} else {
2018-08-01 17:58:03 +00:00
if (g_input_buffer_length < sizeof(g_input_buffer) - 1) {
volatile uint8_t *loc = cursor_pos();
2018-07-31 22:03:06 +00:00
*loc = key | 0x80;
2018-08-01 17:58:03 +00:00
move_cursor(g_cursor_x + 1, g_cursor_y);
2018-07-31 22:03:06 +00:00
2018-08-01 17:58:03 +00:00
g_input_buffer[g_input_buffer_length++] = key;
2018-07-31 22:03:06 +00:00
}
2018-07-31 19:48:29 +00:00
}
}
2018-07-31 19:02:26 +00:00
2018-07-31 19:48:29 +00:00
show_cursor();
2018-07-31 19:02:26 +00:00
}
2018-07-31 07:05:22 +00:00
}
return 0;
2018-07-28 05:30:44 +00:00
}